<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <!--[if IE]>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
    <meta content="width=device-width, initial-scale=1.0" name="viewport">
    <meta content="Asciidoctor 1.5.6.1" name="generator">
    <meta content="Stephane Maldini, Simon Baslé" name="author">
    <title>Reactor 3 参考文档</title>
    <style>
        @import url(https://fonts.googleapis.com/css?family=Montserrat:400,700);
        @import url(http://cdnjs.cloudflare.com/ajax/libs/semantic-ui/1.6.2/semantic.min.css);


        #header .details br + span.author:before {
            content: "\00a0\0026\00a0";
            color: rgba(0, 0, 0, .85);
        }

        #header .details br + span.email:before {
            content: "(";
        }

        #header .details br + span.email:after {
            content: ")";
        }

        /*! normalize.css v2.1.2 | MIT License | git.io/normalize */
        /* ========================================================================== HTML5 display definitions ========================================================================== */
        /** Correct `block` display not defined in IE 8/9. */
        @import url(http://cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.1/css/font-awesome.css);

        article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary {
            display: block;
        }

        /** Correct `inline-block` display not defined in IE 8/9. */
        audio, canvas, video {
            display: inline-block;
        }

        /** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */
        audio:not([controls]) {
            display: none;
            height: 0;
        }

        /** Address `[hidden]` styling not present in IE 8/9. Hide the `template` element in IE, Safari, and Firefox < 22. */
        [hidden], template {
            display: none;
        }

        script {
            display: none !important;
        }

        /* ========================================================================== Base ========================================================================== */
        /** 1. Set default font family to sans-serif. 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. */
        html {
            font-family: sans-serif; /* 1 */
            -ms-text-size-adjust: 100%; /* 2 */
            -webkit-text-size-adjust: 100%; /* 2 */
        }

        /** Remove default margin. */
        body {
            margin: 0;
        }

        /* ========================================================================== Links ========================================================================== */
        /** Remove the gray background color from active links in IE 10. */
        a {
            background: transparent;
        }

        /** Address `outline` inconsistency between Chrome and other browsers. */
        a:focus {
            outline: thin dotted;
        }

        /** Improve readability when focused and also mouse hovered in all browsers. */
        a:active, a:hover {
            outline: 0;
        }

        /* ========================================================================== Typography ========================================================================== */
        /** Address variable `h1` font-size and margin within `section` and `article` contexts in Firefox 4+, Safari 5, and Chrome. */
        h1 {
            font-size: 2em;
            margin: 1.2em 0;
        }

        /** Address styling not present in IE 8/9, Safari 5, and Chrome. */
        abbr[title] {
            border-bottom: 1px dotted;
        }

        /** Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. */
        b, strong {
            font-weight: bold;
        }

        /** Address styling not present in Safari 5 and Chrome. */
        dfn {
            font-style: italic;
        }

        /** Address differences between Firefox and other browsers. */
        hr {
            -moz-box-sizing: content-box;
            box-sizing: content-box;
            height: 0;
        }

        /** Address styling not present in IE 8/9. */
        mark {
            background: #ff0;
            color: #000;
        }

        /** Correct font family set oddly in Safari 5 and Chrome. */
        code, kbd, pre, samp {
            font-family: monospace, serif;
            font-size: 1em;
        }

        /** Improve readability of pre-formatted text in all browsers. */
        pre {
            white-space: pre-wrap;
        }

        /** Set consistent quote types. */
        q {
            quotes: "\201C" "\201D" "\2018" "\2019";
        }

        /** Address inconsistent and variable font size in all browsers. */
        small {
            font-size: 80%;
        }

        /** Prevent `sub` and `sup` affecting `line-height` in all browsers. */
        sub, sup {
            font-size: 75%;
            line-height: 0;
            position: relative;
            vertical-align: baseline;
        }

        sup {
            top: -0.5em;
        }

        sub {
            bottom: -0.25em;
        }

        /* ========================================================================== Embedded content ========================================================================== */
        /** Remove border when inside `a` element in IE 8/9. */
        img {
            border: 0;
        }

        /** Correct overflow displayed oddly in IE 9. */
        svg:not(:root) {
            overflow: hidden;
        }

        /* ========================================================================== Figures ========================================================================== */
        /** Address margin not present in IE 8/9 and Safari 5. */
        figure {
            margin: 0;
        }

        /* ========================================================================== Forms ========================================================================== */
        /** Define consistent border, margin, and padding. */
        fieldset {
            border: 1px solid #c0c0c0;
            margin: 0 2px;
            padding: 0.35em 0.625em 0.75em;
        }

        /** 1. Correct `color` not being inherited in IE 8/9. 2. Remove padding so people aren't caught out if they zero out fieldsets. */
        legend {
            border: 0; /* 1 */
            padding: 0; /* 2 */
        }

        /** 1. Correct font family not being inherited in all browsers. 2. Correct font size not being inherited in all browsers. 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. */
        button, input, select, textarea {
            font-family: inherit; /* 1 */
            font-size: 100%; /* 2 */
            margin: 0; /* 3 */
        }

        /** Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet. */
        button, input {
            line-height: normal;
        }

        /** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. Correct `select` style inheritance in Firefox 4+ and Opera. */
        button, select {
            text-transform: none;
        }

        /** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. */
        button, html input[type="button"], input[type="reset"], input[type="submit"] {
            -webkit-appearance: button; /* 2 */
            cursor: pointer; /* 3 */
        }

        /** Re-set default cursor for disabled elements. */
        button[disabled], html input[disabled] {
            cursor: default;
        }

        /** 1. Address box sizing set to `content-box` in IE 8/9. 2. Remove excess padding in IE 8/9. */
        input[type="checkbox"], input[type="radio"] {
            box-sizing: border-box; /* 1 */
            padding: 0; /* 2 */
        }

        /** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */
        input[type="search"] {
            -webkit-appearance: textfield; /* 1 */
            -moz-box-sizing: content-box;
            -webkit-box-sizing: content-box; /* 2 */
            box-sizing: content-box;
        }

        /** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */
        input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {
            -webkit-appearance: none;
        }

        /** Remove inner padding and border in Firefox 4+. */
        button::-moz-focus-inner, input::-moz-focus-inner {
            border: 0;
            padding: 0;
        }

        /** 1. Remove default vertical scrollbar in IE 8/9. 2. Improve readability and alignment in all browsers. */
        textarea {
            overflow: auto; /* 1 */
            vertical-align: top; /* 2 */
        }

        /* ========================================================================== Tables ========================================================================== */
        /** Remove most spacing between table cells. */
        table {
            border-collapse: collapse;
            border-spacing: 0;
        }

        meta.foundation-mq-small {
            font-family: "only screen and (min-width: 768px)";
            width: 768px;
        }

        meta.foundation-mq-medium {
            font-family: "only screen and (min-width:1280px)";
            width: 1280px;
        }

        meta.foundation-mq-large {
            font-family: "only screen and (min-width:1440px)";
            width: 1440px;
        }

        *, *:before, *:after {
            -moz-box-sizing: border-box;
            -webkit-box-sizing: border-box;
            box-sizing: border-box;
        }

        html, body {
            font-size: 100%;
        }

        body {
            background: white;
            color: #34302d;
            padding: 0;
            margin: 0;
            font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
            font-weight: normal;
            font-style: normal;
            line-height: 1.8em;
            position: relative;
            cursor: auto;
        }

        #content, #content p {
            line-height: 1.8em;
            margin-top: 1.5em;
        }

        #content li p {
            margin-top: 0.25em;
        }

        a:hover {
            cursor: pointer;
        }

        img, object, embed {
            max-width: 100%;
            height: auto;
        }

        object, embed {
            height: 100%;
        }

        img {
            -ms-interpolation-mode: bicubic;
        }

        #map_canvas img, #map_canvas embed, #map_canvas object, .map_canvas img, .map_canvas embed, .map_canvas object {
            max-width: none !important;
        }

        .left {
            float: left !important;
        }

        .right {
            float: right !important;
        }

        .text-left {
            text-align: left !important;
        }

        .text-right {
            text-align: right !important;
        }

        .text-center {
            text-align: center !important;
        }

        .text-justify {
            text-align: justify !important;
        }

        .hide {
            display: none;
        }

        .antialiased, body {
            -webkit-font-smoothing: antialiased;
        }

        img {
            display: inline-block;
            vertical-align: middle;
        }

        textarea {
            height: auto;
            min-height: 50px;
        }

        select {
            width: 100%;
        }

        p.lead, .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p {
            font-size: 1.21875em;
        }

        .subheader, #content #toctitle, .admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .mathblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title, .tableblock > caption {
            color: #6db33f;
            font-weight: 300;
            margin-top: 0.2em;
            margin-bottom: 0.5em;
        }

        /* Typography resets */
        div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td {
            margin: 0;
            padding: 0;
            direction: ltr;
        }

        /* Default Link Styles */
        a {
            color: #6db33f;
            line-height: inherit;
            text-decoration: none;
        }

        a:hover, a:focus {
            color: #6db33f;
            text-decoration: underline;
        }

        a img {
            border: none;
        }

        /* Default paragraph styles */
        p {
            font-family: inherit;
            font-weight: normal;
            font-size: 1em;
            margin-bottom: 1.25em;
            text-rendering: optimizeLegibility;
        }

        p aside {
            font-size: 0.875em;
            font-style: italic;
        }

        /* Default header styles */
        h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 {
            font-family: "Montserrat", Arial, sans-serif;
            font-weight: normal;
            font-style: normal;
            color: #34302d;
            text-rendering: optimizeLegibility;
            margin-top: 1.6em;
            margin-bottom: 0.6em;
        }

        h1 small, h2 small, h3 small, #toctitle small, .sidebarblock > .content > .title small, h4 small, h5 small, h6 small {
            font-size: 60%;
            color: #6db33f;
            line-height: 0;
        }

        h1 {
            font-size: 2.125em;
        }

        h2 {
            font-size: 1.6875em;
        }

        h3, #toctitle, .sidebarblock > .content > .title {
            font-size: 1.375em;
        }

        h4 {
            font-size: 1.125em;
        }

        h5 {
            font-size: 1.125em;
        }

        h6 {
            font-size: 1em;
        }

        hr {
            border: solid #dcd2c9;
            border-width: 1px 0 0;
            clear: both;
            margin: 1.25em 0 1.1875em;
            height: 0;
        }

        /* Helpful Typography Defaults */
        em, i {
            font-style: italic;
            line-height: inherit;
        }

        strong, b {
            font-weight: bold;
            line-height: inherit;
        }

        small {
            font-size: 60%;
            line-height: inherit;
        }

        code {
            font-family: Consolas, "Liberation Mono", Courier, monospace;
            font-weight: bold;
            color: #305CB5;
        }

        /* Lists */
        ul, ol, dl {
            font-size: 1em;
            margin-bottom: 1.25em;
            list-style-position: outside;
            font-family: inherit;
        }

        ul, ol {
            margin-left: 1.5em;
        }

        ul.no-bullet, ol.no-bullet {
            margin-left: 1.5em;
        }

        /* Unordered Lists */
        ul li ul, ul li ol {
            margin-left: 1.25em;
            margin-bottom: 0;
            font-size: 1em; /* Override nested font-size change */
        }

        ul.square li ul, ul.circle li ul, ul.disc li ul {
            list-style: inherit;
        }

        ul.square {
            list-style-type: square;
        }

        ul.circle {
            list-style-type: circle;
        }

        ul.disc {
            list-style-type: disc;
        }

        ul.no-bullet {
            list-style: none;
        }

        /* Ordered Lists */
        ol li ul, ol li ol {
            margin-left: 1.25em;
            margin-bottom: 0;
        }

        /* Definition Lists */
        dl dt {
            margin-bottom: 0.3125em;
            font-weight: bold;
        }

        dl dd {
            margin-bottom: 1.25em;
        }

        /* Abbreviations */
        abbr, acronym {
            text-transform: uppercase;
            font-size: 90%;
            color: #34302d;
            border-bottom: 1px dotted #dddddd;
            cursor: help;
        }

        abbr {
            text-transform: none;
        }

        /* Blockquotes */
        blockquote {
            margin: 0 0 1.25em;
            padding: 0.5625em 1.25em 0 1.1875em;
            border-left: 1px solid #dddddd;
        }

        blockquote cite {
            display: block;
            font-size: 0.8125em;
            color: #655241;
        }

        blockquote cite:before {
            content: "\2014 \0020";
        }

        blockquote cite a, blockquote cite a:visited {
            color: #655241;
        }

        blockquote, blockquote p {
            color: #34302d;
        }

        /* Microformats */
        .vcard {
            display: inline-block;
            margin: 0 0 1.25em 0;
            border: 1px solid #dddddd;
            padding: 0.625em 0.75em;
        }

        .vcard li {
            margin: 0;
            display: block;
        }

        .vcard .fn {
            font-weight: bold;
            font-size: 0.9375em;
        }

        .vevent .summary {
            font-weight: bold;
        }

        .vevent abbr {
            cursor: auto;
            text-decoration: none;
            font-weight: bold;
            border: none;
            padding: 0 0.0625em;
        }

        @media only screen and (min-width: 768px) {
            h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 {
            }

            h1 {
                font-size: 2.75em;
            }

            h2 {
                font-size: 2.3125em;
            }

            h3, #toctitle, .sidebarblock > .content > .title {
                font-size: 1.6875em;
            }

            h4 {
                font-size: 1.4375em;
            }
        }

        /* Print styles.  Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/ Credit to Paul Irish and HTML5 Boilerplate (html5boilerplate.com)
*/
        .print-only {
            display: none !important;
        }

        @media print {
            * {
                background: transparent !important;
                color: #000 !important; /* Black prints faster: h5bp.com/s */
                box-shadow: none !important;
                text-shadow: none !important;
            }

            a, a:visited {
                text-decoration: underline;
            }

            a[href]:after {
                content: " (" attr(href) ")";
            }

            abbr[title]:after {
                content: " (" attr(title) ")";
            }

            .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after {
                content: "";
            }

            pre, blockquote {
                border: 1px solid #999;
                page-break-inside: avoid;
            }

            thead {
                display: table-header-group; /* h5bp.com/t */
            }

            tr, img {
                page-break-inside: avoid;
            }

            img {
                max-width: 100% !important;
            }

            @page {
                margin: 0.5cm;
            }

            p, h2, h3, #toctitle, .sidebarblock > .content > .title {
                orphans: 3;
                widows: 3;
            }

            h2, h3, #toctitle, .sidebarblock > .content > .title {
                page-break-after: avoid;
            }

            .hide-on-print {
                display: none !important;
            }

            .print-only {
                display: block !important;
            }

            .hide-for-print {
                display: none !important;
            }

            .show-for-print {
                display: inherit !important;
            }
        }

        /* Tables */
        table {
            background: white;
            margin-bottom: 1.25em;
            border: solid 1px #34302d;
        }

        table thead, table tfoot {
            font-weight: bold;
        }

        table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td {
            padding: 0.5em 0.625em 0.625em;
            font-size: inherit;
            color: #34302d;
            text-align: left;
        }

        table thead tr th {
            color: white;
            background: #34302d;
        }

        table tr th, table tr td {
            padding: 0.5625em 0.625em;
            font-size: inherit;
            color: #34302d;
            border: 0 none;
        }

        table tr.even, table tr.alt, table tr:nth-of-type(even) {
            background: #f2F2F2;
        }

        table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td {
            display: table-cell;
        }

        .clearfix:before, .clearfix:after, .float-group:before, .float-group:after {
            content: " ";
            display: table;
        }

        .clearfix:after, .float-group:after {
            clear: both;
        }

        *:not(pre) > code {
            font-size: inherit;
            padding: 0;
            white-space: nowrap;
            background-color: inherit;
            border: 0 solid #dddddd;
            -webkit-border-radius: 6px;
            border-radius: 6px;
            text-shadow: none;
        }

        pre, pre > code {
            color: black;
            font-family: monospace, serif;
            font-weight: normal;
        }

        .keyseq {
            color: #774417;
        }

        kbd:not(.keyseq) {
            display: inline-block;
            color: #211306;
            font-size: 0.75em;
            background-color: #F7F7F7;
            border: 1px solid #ccc;
            -webkit-border-radius: 3px;
            border-radius: 3px;
            -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset;
            box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset;
            margin: -0.15em 0.15em 0 0.15em;
            padding: 0.2em 0.6em 0.2em 0.5em;
            vertical-align: middle;
            white-space: nowrap;
        }

        .keyseq kbd:first-child {
            margin-left: 0;
        }

        .keyseq kbd:last-child {
            margin-right: 0;
        }

        .menuseq, .menu {
            color: black;
        }

        b.button:before, b.button:after {
            position: relative;
            top: -1px;
            font-weight: normal;
        }

        b.button:before {
            content: "[";
            padding: 0 3px 0 2px;
        }

        b.button:after {
            content: "]";
            padding: 0 2px 0 3px;
        }

        p a > code:hover {
            color: #541312;
        }

        #header, #content, #footnotes, #footer {
            width: 100%;
            margin-left: auto;
            margin-right: auto;
            margin-top: 0;
            margin-bottom: 0;
            max-width: 62.5em;
            *zoom: 1;
            position: relative;
            padding-left: 4em;
            padding-right: 4em;
        }

        #header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after {
            content: " ";
            display: table;
        }

        #header:after, #content:after, #footnotes:after, #footer:after {
            clear: both;
        }

        #header {
            margin-bottom: 2.5em;
        }

        #header > h1 {
            color: #34302d;
            font-weight: 400;
        }

        #header span {
            color: #34302d;
        }

        #header #revnumber {
            text-transform: capitalize;
        }

        #header br {
            display: none;
        }

        #header br + span {
        }

        #revdate {
            display: block;
        }

        #toc {
            border-bottom: 1px solid #e6dfd8;
            padding-bottom: 1.25em;
        }

        #toc > ul {
            margin-left: 0.25em;
        }

        #toc ul.sectlevel0 > li > a {
            font-style: italic;
        }

        #toc ul.sectlevel0 ul.sectlevel1 {
            margin-left: 0;
            margin-top: 0.5em;
            margin-bottom: 0.5em;
        }

        #toc ul {
            list-style-type: none;
        }

        #toctitle {
            color: #385dbd;
        }

        @media only screen and (min-width: 768px) {
            body.toc2 {
                padding-left: 15em;
                padding-right: 0;
            }

            #toc.toc2 {
                position: fixed;
                width: 15em;
                left: 0;
                border-bottom: 0;
                z-index: 1000;
                padding: 1em;
                height: 100%;
                top: 0px;
                background: #F1F1F1;
                overflow: auto;

                -moz-transition-property: top;
                -o-transition-property: top;
                -webkit-transition-property: top;
                transition-property: top;
                -moz-transition-duration: 0.4s;
                -o-transition-duration: 0.4s;
                -webkit-transition-duration: 0.4s;
                transition-duration: 0.4s;
            }

            #reactor-header {
                position: fixed;
                top: -75px;
                left: 0;
                right: 0;
                height: 75px;


                -moz-transition-property: top;
                -o-transition-property: top;
                -webkit-transition-property: top;
                transition-property: top;
                -moz-transition-duration: 0.4s;
                -o-transition-duration: 0.4s;
                -webkit-transition-duration: 0.4s;
                transition-duration: 0.4s;
            }

            body.head-show #toc.toc2 {
                top: 75px;
            }

            body.head-show #reactor-header {
                top: 0;
            }

            #toc.toc2 a {
                color: #34302d;
                font-family: Montserrat;
            }

            #toc.toc2 #toctitle {
                margin-top: 0;
                font-size: 1.2em;
            }

            #toc.toc2 > ul {
                font-size: .90em;
            }

            #toc.toc2 ul ul {
                margin-left: 0;
                padding-left: 0.4em;
            }

            #toc.toc2 ul.sectlevel0 ul.sectlevel1 {
                padding-left: 0;
                margin-top: 0.5em;
                margin-bottom: 0.5em;
            }

            body.toc2.toc-right {
                padding-left: 0;
                padding-right: 15em;
            }

            body.toc2.toc-right #toc.toc2 {
                border-right: 0;
                border-left: 1px solid #e6dfd8;
                left: auto;
                right: 0;
            }
        }

        @media only screen and (min-width: 1280px) {
            body.toc2 {
                padding-left: 20em;
                padding-right: 0;
            }

            #toc.toc2 {
                width: 20em;
            }

            #toc.toc2 #toctitle {
                font-size: 1.375em;
            }

            #toc.toc2 > ul {
                font-size: 0.95em;
            }

            #toc.toc2 ul ul {
                padding-left: 1.25em;
            }

            body.toc2.toc-right {
                padding-left: 0;
                padding-right: 20em;
            }
        }

        #content #toc {
            border-style: solid;
            border-width: 1px;
            border-color: #d9d9d9;
            margin-bottom: 1.25em;
            padding: 1.25em;
            background: #f2f2f2;
            border-width: 0;
            -webkit-border-radius: 6px;
            border-radius: 6px;
        }

        #content #toc > :first-child {
            margin-top: 0;
        }

        #content #toc > :last-child {
            margin-bottom: 0;
        }

        #content #toc a {
            text-decoration: none;
        }

        #content #toctitle {
            font-weight: bold;
            font-family: "Montserrat", Arial, sans-serif;
            font-size: 1em;
            padding-left: 0.125em;
        }

        #footer {
            max-width: 100%;
            background-color: white;
            padding: 1.25em;
            color: #CCC;
            border-top: 3px solid #F1F1F1;
        }

        #footer-text {
            color: #444;
            line-height: 1.44;
        }

        .sect1 {
            padding-bottom: 1.25em;
        }

        .sect1 + .sect1 {
            border-top: 1px solid #e6dfd8;
        }

        #content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor {
            position: absolute;
            width: 1em;
            margin-left: -1em;
            display: block;
            text-decoration: none;
            visibility: hidden;
            text-align: center;
            font-weight: normal;
        }

        #content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before {
            content: '\00A7';
            font-size: .85em;
            vertical-align: text-top;
            display: block;
            margin-top: 0.05em;
        }

        #content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover {
            visibility: visible;
        }

        #content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link {
            color: #34302d;
            text-decoration: none;
        }

        #content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover {
            color: #34302d;
        }

        .imageblock, .literalblock, .listingblock, .mathblock, .verseblock, .videoblock {
            margin-bottom: 1.25em;
            margin-top: 1.25em;
        }

        .admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .listingblock > .title, .literalblock > .title, .mathblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .videoblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title {
            text-align: left;
            font-weight: bold;
        }

        .tableblock > caption {
            text-align: left;
            font-weight: bold;
            white-space: nowrap;
            overflow: visible;
            max-width: 0;
        }

        table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p {
            font-size: inherit;
        }

        .admonitionblock > table {
            border: 0;
            background: none;
            width: 100%;
        }

        .admonitionblock > table td.icon {
            text-align: center;
            width: 80px;
        }

        .admonitionblock > table td.icon img {
            max-width: none;
        }

        .admonitionblock > table td.icon .title {
            font-weight: bold;
            text-transform: uppercase;
        }

        .admonitionblock > table td.content {
            padding-left: 1.125em;
            padding-right: 1.25em;
            border-left: 1px solid #dcd2c9;
            color: #34302d;
        }

        .admonitionblock > table td.content > :last-child > :last-child {
            margin-bottom: 0;
        }

        .exampleblock > .content {
            border-style: solid;
            border-width: 1px;
            border-color: #f3e0ce;
            margin-bottom: 1.25em;
            padding: 1.25em;
            background: white;
            -webkit-border-radius: 6px;
            border-radius: 6px;
        }

        .exampleblock > .content > :first-child {
            margin-top: 0;
        }

        .exampleblock > .content > :last-child {
            margin-bottom: 0;
        }

        .exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6, .exampleblock > .content p {
            color: #333333;
        }

        .exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6 {
            margin-bottom: 0.625em;
        }

        .exampleblock > .content h1.subheader, .exampleblock > .content h2.subheader, .exampleblock > .content h3.subheader, .exampleblock > .content .subheader#toctitle, .sidebarblock.exampleblock > .content > .subheader.title, .exampleblock > .content h4.subheader, .exampleblock > .content h5.subheader, .exampleblock > .content h6.subheader {
        }

        .exampleblock.result > .content {
            -webkit-box-shadow: 0 1px 8px #d9d9d9;
            box-shadow: 0 1px 8px #d9d9d9;
        }

        .sidebarblock {
            padding: 1.25em 2em;
            background: #F1F1F1;
            margin: 2em -2em;

        }

        .sidebarblock > :first-child {
            margin-top: 0;
        }

        .sidebarblock > :last-child {
            margin-bottom: 0;
        }

        .sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6, .sidebarblock p {
            color: #333333;
        }

        .sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6 {
            margin-bottom: 0.625em;
        }

        .sidebarblock h1.subheader, .sidebarblock h2.subheader, .sidebarblock h3.subheader, .sidebarblock .subheader#toctitle, .sidebarblock > .content > .subheader.title, .sidebarblock h4.subheader, .sidebarblock h5.subheader, .sidebarblock h6.subheader {
        }

        .sidebarblock > .content > .title {
            color: #6db33f;
            margin-top: 0;
            font-size: 1.2em;
        }

        .exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child {
            margin-bottom: 0;
        }

        .literalblock pre:not([class]), .listingblock pre:not([class]) {
            background: url('../images/golo/pre-bg.png?1370460826');
        }

        .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] {
            border-width: 1px;
            border-style: solid;
            border-color: rgba(21, 35, 71, 0.1);
            -webkit-border-radius: 6px;
            border-radius: 6px;
            padding: 0.8em;
            word-wrap: break-word;
        }

        .literalblock pre.nowrap, .literalblock pre[class].nowrap, .listingblock pre.nowrap, .listingblock pre[class].nowrap {
            overflow-x: auto;
            white-space: pre;
            word-wrap: normal;
        }

        .literalblock pre > code, .literalblock pre[class] > code, .listingblock pre > code, .listingblock pre[class] > code {
            display: block;
        }

        @media only screen {
            .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] {
                font-size: 0.72em;
            }
        }

        @media only screen and (min-width: 768px) {
            .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] {
                font-size: 0.81em;
            }
        }

        @media only screen and (min-width: 1280px) {
            .literalblock pre, .literalblock pre[class], .listingblock pre, .listingblock pre[class] {
                font-size: 0.9em;
            }
        }

        .listingblock pre.highlight {
            padding: 0;
            line-height: 1em;
        }

        .listingblock pre.highlight > code {
            padding: 0.8em;
        }

        .listingblock > .content {
            position: relative;
        }

        .listingblock:hover code[class*=" language-"]:before {
            text-transform: uppercase;
            font-size: 0.9em;
            color: #999;
            position: absolute;
            top: 0.375em;
            right: 0.375em;
        }

        .listingblock:hover code.asciidoc:before {
            content: "asciidoc";
        }

        .listingblock:hover code.clojure:before {
            content: "clojure";
        }

        .listingblock:hover code.css:before {
            content: "css";
        }

        .listingblock:hover code.groovy:before {
            content: "groovy";
        }

        .listingblock:hover code.html:before {
            content: "html";
        }

        .listingblock:hover code.java:before {
            content: "java";
        }

        .listingblock:hover code.javascript:before {
            content: "javascript";
        }

        .listingblock:hover code.python:before {
            content: "python";
        }

        .listingblock:hover code.ruby:before {
            content: "ruby";
        }

        .listingblock:hover code.sass:before {
            content: "sass";
        }

        .listingblock:hover code.scss:before {
            content: "scss";
        }

        .listingblock:hover code.xml:before {
            content: "xml";
        }

        .listingblock:hover code.yaml:before {
            content: "yaml";
        }

        .listingblock.terminal pre .command:before {
            content: attr(data-prompt);
            padding-right: 0.5em;
            color: #999;
        }

        .listingblock.terminal pre .command:not([data-prompt]):before {
            content: '$';
        }

        table.pyhltable {
            border: 0;
            margin-bottom: 0;
        }

        table.pyhltable td {
            vertical-align: top;
            padding-top: 0;
            padding-bottom: 0;
        }

        table.pyhltable td.code {
            padding-left: .75em;
            padding-right: 0;
        }

        .highlight.pygments .lineno, table.pyhltable td:not(.code) {
            color: #999;
            padding-left: 0;
            padding-right: .5em;
            border-right: 1px solid #dcd2c9;
        }

        .highlight.pygments .lineno {
            display: inline-block;
            margin-right: .25em;
        }

        table.pyhltable .linenodiv {
            background-color: transparent !important;
            padding-right: 0 !important;
        }

        .quoteblock {
            margin: 0 0 1.25em;
            padding: 0.5625em 1.25em 0 1.1875em;
            border-left: 3px solid #dddddd;
        }

        .quoteblock blockquote {
            margin: 0 0 1.25em 0;
            padding: 0 0 0.5625em 0;
            border: 0;
        }

        .quoteblock blockquote > .paragraph:last-child p {
            margin-bottom: 0;
        }

        .quoteblock .attribution {
            margin-top: -.25em;
            padding-bottom: 0.5625em;
            font-size: 0.8125em;
        }

        .quoteblock .attribution br {
            display: none;
        }

        .quoteblock .attribution cite {
            display: block;
            margin-bottom: 0.625em;
        }

        table thead th, table tfoot th {
            font-weight: bold;
        }

        table.tableblock.grid-all {
            border-collapse: separate;
            border-radius: 6px;
            border-top: 1px solid #34302d;
            border-bottom: 1px solid #34302d;
        }

        table.tableblock.frame-topbot, table.tableblock.frame-none {
            border-left: 0;
            border-right: 0;
        }

        table.tableblock.frame-sides, table.tableblock.frame-none {
            border-top: 0;
            border-bottom: 0;
        }

        table.tableblock td .paragraph:last-child p > p:last-child, table.tableblock th > p:last-child, table.tableblock td > p:last-child {
            margin-bottom: 0;
        }

        th.tableblock.halign-left, td.tableblock.halign-left {
            text-align: left;
        }

        th.tableblock.halign-right, td.tableblock.halign-right {
            text-align: right;
        }

        th.tableblock.halign-center, td.tableblock.halign-center {
            text-align: center;
        }

        th.tableblock.valign-top, td.tableblock.valign-top {
            vertical-align: top;
        }

        th.tableblock.valign-bottom, td.tableblock.valign-bottom {
            vertical-align: bottom;
        }

        th.tableblock.valign-middle, td.tableblock.valign-middle {
            vertical-align: middle;
        }

        tbody tr th {
            display: table-cell;
            background: rgba(105, 60, 22, 0.25);
        }

        tbody tr th, tbody tr th p, tfoot tr th, tfoot tr th p {
            color: #211306;
            font-weight: bold;
        }

        td > div.verse {
            white-space: pre;
        }

        ol {
            margin-left: 1.75em;
        }

        ul li ol {
            margin-left: 1.5em;
        }

        dl dd {
            margin-left: 1.125em;
        }

        dl dd:last-child, dl dd:last-child > :last-child {
            margin-bottom: 0;
        }

        ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist {
            margin-bottom: 0.625em;
        }

        ul.unstyled, ol.unnumbered, ul.checklist, ul.none {
            list-style-type: none;
        }

        ul.unstyled, ol.unnumbered, ul.checklist {
            margin-left: 0.625em;
        }

        ul.checklist li > p:first-child > i[class^="icon-check"]:first-child, ul.checklist li > p:first-child > input[type="checkbox"]:first-child {
            margin-right: 0.25em;
        }

        ul.checklist li > p:first-child > input[type="checkbox"]:first-child {
            position: relative;
            top: 1px;
        }

        ul.inline {
            margin: 0 auto 0.625em auto;
            margin-left: -1.375em;
            margin-right: 0;
            padding: 0;
            list-style: none;
            overflow: hidden;
        }

        ul.inline > li {
            list-style: none;
            float: left;
            margin-left: 1.375em;
            display: block;
        }

        ul.inline > li > * {
            display: block;
        }

        .unstyled dl dt {
            font-weight: normal;
            font-style: normal;
        }

        ol.arabic {
            list-style-type: decimal;
        }

        ol.decimal {
            list-style-type: decimal-leading-zero;
        }

        ol.loweralpha {
            list-style-type: lower-alpha;
        }

        ol.upperalpha {
            list-style-type: upper-alpha;
        }

        ol.lowerroman {
            list-style-type: lower-roman;
        }

        ol.upperroman {
            list-style-type: upper-roman;
        }

        ol.lowergreek {
            list-style-type: lower-greek;
        }

        .hdlist > table, .colist > table {
            border: 0;
            background: none;
        }

        .hdlist > table > tbody > tr, .colist > table > tbody > tr {
            background: none;
        }

        td.hdlist1 {
            padding-right: .75em;
            font-weight: bold;
        }

        td.hdlist1, td.hdlist2 {
            vertical-align: top;
        }

        .literalblock + .colist, .listingblock + .colist {
            margin-top: -0.5em;
        }

        .colist > table tr > td:first-of-type {
            padding: 0 .75em;
        }

        .colist > table tr > td:last-of-type {
            padding: 0.25em 0;
        }

        .qanda > ol > li > p > em:only-child {
            color: #063f40;
        }

        .thumb, .th {
            line-height: 0;
            display: inline-block;
            border: solid 4px white;
            -webkit-box-shadow: 0 0 0 1px #dddddd;
            box-shadow: 0 0 0 1px #dddddd;
        }

        .imageblock.left, .imageblock[style*="float: left"] {
            margin: 0.25em 0.625em 1.25em 0;
        }

        .imageblock.right, .imageblock[style*="float: right"] {
            margin: 0.25em 0 1.25em 0.625em;
        }

        .imageblock > .title {
            margin-bottom: 0;
        }

        .imageblock.thumb, .imageblock.th {
            border-width: 6px;
        }

        .imageblock.thumb > .title, .imageblock.th > .title {
            padding: 0 0.125em;
        }

        .image.left, .image.right {
            margin-top: 0.25em;
            margin-bottom: 0.25em;
            display: inline-block;
            line-height: 0;
        }

        .image.left {
            margin-right: 0.625em;
        }

        .image.right {
            margin-left: 0.625em;
        }

        a.image {
            text-decoration: none;
        }

        span.footnote, span.footnoteref {
            vertical-align: super;
            font-size: 0.875em;
        }

        span.footnote a, span.footnoteref a {
            text-decoration: none;
        }

        #footnotes {
            padding-top: 0.75em;
            padding-bottom: 0.75em;
            margin-bottom: 0.625em;
        }

        #footnotes hr {
            width: 20%;
            min-width: 6.25em;
            margin: -.25em 0 .75em 0;
            border-width: 1px 0 0 0;
        }

        #footnotes .footnote {
            padding: 0 0.375em;
            font-size: 0.875em;
            margin-left: 1.2em;
            text-indent: -1.2em;
            margin-bottom: .2em;
        }

        #footnotes .footnote a:first-of-type {
            font-weight: bold;
            text-decoration: none;
        }

        #footnotes .footnote:last-of-type {
            margin-bottom: 0;
        }

        #content #footnotes {
            margin-top: -0.625em;
            margin-bottom: 0;
            padding: 0.75em 0;
        }

        .gist .file-data > table {
            border: none;
            background: #fff;
            width: 100%;
            margin-bottom: 0;
        }

        .gist .file-data > table td.line-data {
            width: 99%;
        }

        div.unbreakable {
            page-break-inside: avoid;
        }

        .big {
            font-size: larger;
        }

        .small {
            font-size: smaller;
        }

        .underline {
            text-decoration: underline;
        }

        .overline {
            text-decoration: overline;
        }

        .line-through {
            text-decoration: line-through;
        }

        .aqua {
            color: #00bfbf;
        }

        .aqua-background {
            background-color: #00fafa;
        }

        .black {
            color: black;
        }

        .black-background {
            background-color: black;
        }

        .blue {
            color: #0000bf;
        }

        .blue-background {
            background-color: #0000fa;
        }

        .fuchsia {
            color: #bf00bf;
        }

        .fuchsia-background {
            background-color: #fa00fa;
        }

        .gray {
            color: #606060;
        }

        .gray-background {
            background-color: #7d7d7d;
        }

        .green {
            color: #006000;
        }

        .green-background {
            background-color: #007d00;
        }

        .lime {
            color: #00bf00;
        }

        .lime-background {
            background-color: #00fa00;
        }

        .maroon {
            color: #600000;
        }

        .maroon-background {
            background-color: #7d0000;
        }

        .navy {
            color: #000060;
        }

        .navy-background {
            background-color: #00007d;
        }

        .olive {
            color: #606000;
        }

        .olive-background {
            background-color: #7d7d00;
        }

        .purple {
            color: #600060;
        }

        .purple-background {
            background-color: #7d007d;
        }

        .red {
            color: #bf0000;
        }

        .red-background {
            background-color: #fa0000;
        }

        .silver {
            color: #909090;
        }

        .silver-background {
            background-color: #bcbcbc;
        }

        .teal {
            color: #006060;
        }

        .teal-background {
            background-color: #007d7d;
        }

        .white {
            color: #bfbfbf;
        }

        .white-background {
            background-color: #fafafa;
        }

        .yellow {
            color: #bfbf00;
        }

        .yellow-background {
            background-color: #fafa00;
        }

        span.icon > [class^="icon-"], span.icon > [class*=" icon-"] {
            cursor: default;
        }

        .admonitionblock td.icon [class^="icon-"]:before {
            font-size: 2.5em;
            text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
            cursor: default;
        }

        .admonitionblock td.icon .icon-note:before {
            content: "\f05a";
            color: #095557;
            color: #064042;
        }

        .admonitionblock td.icon .icon-tip:before {
            content: "\f0eb";
            text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8);
            color: #111;
        }

        .admonitionblock td.icon .icon-warning:before {
            content: "\f071";
            color: #bf6900;
        }

        .admonitionblock td.icon .icon-caution:before {
            content: "\f06d";
            color: #bf3400;
        }

        .admonitionblock td.icon .icon-important:before {
            content: "\f06a";
            color: #bf0000;
        }

        .conum {
            display: inline-block;
            color: white !important;
            background-color: #211306;
            -webkit-border-radius: 100px;
            border-radius: 100px;
            text-align: center;
            width: 20px;
            height: 20px;
            font-size: 12px;
            font-weight: bold;
            line-height: 20px;
            font-family: Arial, sans-serif;
            font-style: normal;
            position: relative;
            top: -2px;
            letter-spacing: -1px;
        }

        .conum * {
            color: white !important;
        }

        .conum + b {
            display: none;
        }

        .conum:after {
            content: attr(data-value);
        }

        .conum:not([data-value]):empty {
            display: none;
        }

        body {
            padding-top: 60px;
        }

        #toc.toc2 ul ul {
            padding-left: 1em;
        }

        #toc.toc2 ul ul.sectlevel2 {
        }

        #toctitle {
            color: #34302d;
            display: none;
        }

        #header h1 {
            font-weight: bold;
            position: relative;
            left: -0.0625em;
        }

        #header h1 span.lo {
            color: #dc9424;
        }

        #content h2, #content h3, #content #toctitle, #content .sidebarblock > .content > .title, #content h4, #content h5, #content #toctitle {
            font-weight: normal;
            position: relative;
            left: -0.0625em;
        }

        #content h2 {
            font-weight: bold;
        }

        .literalblock .content pre.highlight, .listingblock .content pre.highlight {
            background: url('../images/golo/pre-bg.png?1370460826');
        }

        .admonitionblock > table td.content {
            border-color: #e6dfd8;
        }

        table.tableblock.grid-all {
            -webkit-border-radius: 0;
            border-radius: 0;
        }

        #footer {
            background-color: #while;
            color: #34302d;
        }

        .imageblock .title {
            text-align: center;
        }

        #content h1.sect0 {
            font-size: 48px;
        }

        #toc > ul > li > a {
            font-size: large;
        }


        @import url(https://fonts.googleapis.com/css?family=Montserrat:400,700|Karla:400,700);
        body {
        }

        #reactor-header {
            background: #34302d;
            border-top: 4px solid #6db33f;
            z-index: 2000;
            font-family: 'Montserrat';
            height: 75px;
        }

        #reactor-header h1#logo {
            margin: 7px 0 0 10px;
            padding: 0;
            float: left;
        }

        #reactor-header h1#logo a {
            display: block;
            background: url(images/logo-2x.png) no-repeat 0 0;
            background-size: 253px 80px;
            height: 40px;
            width: 253px;
            text-indent: -6000em;
            margin: 8px 0;
        }

        #reactor-header h1#logo a:hover strong {
            filter: progid:DXImageTransform.Microsoft.Alpha(enabled=false);
            opacity: 1;
        }

        #reactor-header h1#logo a strong {
            display: block;
            background: url(images/logo-2x.png) no-repeat 0 0;
            background-size: 253px 80px;
            color: red;
            height: 40px;
            width: 253px;
            text-indent: -6000em;
            margin: 8px 0;
            -moz-transition-property: opacity;
            -o-transition-property: opacity;
            -webkit-transition-property: opacity;
            transition-property: opacity;
            -moz-transition-duration: 0.2s;
            -o-transition-duration: 0.2s;
            -webkit-transition-duration: 0.2s;
            transition-duration: 0.2s;
            filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
            opacity: 0;
        }

        #nav, #nav ul {
            display: block;
            margin: 0;
            padding: 0;
        }

        #nav {
            float: right;
            margin-right: 10px;
        }

        #nav ul li {
            display: block;
            float: left;
            list-style: none;
            margin: 0;
            padding: 0;
        }

        #nav ul li a {
            color: white;
            text-decoration: none;
            font-weight: 500;
            display: block;
            text-transform: uppercase;
            font-size: 13.5px;
            line-height: 71px;
            margin: 0;
            padding: 0 12px;
            -moz-transition-property: background-color;
            -o-transition-property: background-color;
            -webkit-transition-property: background-color;
            transition-property: background-color;
            -moz-transition-duration: 0.2s;
            -o-transition-duration: 0.2s;
            -webkit-transition-duration: 0.2s;
            transition-duration: 0.2s;
        }

        #nav ul li a:hover {
            background: #6db33f;
        }

        #nav ul li a.active {
            background: #6db33f;
        }

    </style>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet">
    <style>
        /* Stylesheet for CodeRay to match GitHub theme | MIT License | http://foundation.zurb.com */
        /*pre.CodeRay {background-color:#f7f7f8;}*/
        .CodeRay .line-numbers {
            border-right: 1px solid #d8d8d8;
            padding: 0 0.5em 0 .25em
        }

        .CodeRay span.line-numbers {
            display: inline-block;
            margin-right: .5em;
            color: rgba(0, 0, 0, .3)
        }

        .CodeRay .line-numbers strong {
            color: rgba(0, 0, 0, .4)
        }

        table.CodeRay {
            border-collapse: separate;
            border-spacing: 0;
            margin-bottom: 0;
            border: 0;
            background: none
        }

        table.CodeRay td {
            vertical-align: top;
            line-height: 1.45
        }

        table.CodeRay td.line-numbers {
            text-align: right
        }

        table.CodeRay td.line-numbers > pre {
            padding: 0;
            color: rgba(0, 0, 0, .3)
        }

        table.CodeRay td.code {
            padding: 0 0 0 .5em
        }

        table.CodeRay td.code > pre {
            padding: 0
        }

        .CodeRay .debug {
            color: #fff !important;
            background: #000080 !important
        }

        .CodeRay .annotation {
            color: #007
        }

        .CodeRay .attribute-name {
            color: #000080
        }

        .CodeRay .attribute-value {
            color: #700
        }

        .CodeRay .binary {
            color: #509
        }

        .CodeRay .comment {
            color: #998;
            font-style: italic
        }

        .CodeRay .char {
            color: #04d
        }

        .CodeRay .char .content {
            color: #04d
        }

        .CodeRay .char .delimiter {
            color: #039
        }

        .CodeRay .class {
            color: #458;
            font-weight: bold
        }

        .CodeRay .complex {
            color: #a08
        }

        .CodeRay .constant, .CodeRay .predefined-constant {
            color: #008080
        }

        .CodeRay .color {
            color: #099
        }

        .CodeRay .class-variable {
            color: #369
        }

        .CodeRay .decorator {
            color: #b0b
        }

        .CodeRay .definition {
            color: #099
        }

        .CodeRay .delimiter {
            color: #000
        }

        .CodeRay .doc {
            color: #970
        }

        .CodeRay .doctype {
            color: #34b
        }

        .CodeRay .doc-string {
            color: #d42
        }

        .CodeRay .escape {
            color: #666
        }

        .CodeRay .entity {
            color: #800
        }

        .CodeRay .error {
            color: #808
        }

        .CodeRay .exception {
            color: inherit
        }

        .CodeRay .filename {
            color: #099
        }

        .CodeRay .function {
            color: #900;
            font-weight: bold
        }

        .CodeRay .global-variable {
            color: #008080
        }

        .CodeRay .hex {
            color: #058
        }

        .CodeRay .integer, .CodeRay .float {
            color: #099
        }

        .CodeRay .include {
            color: #555
        }

        .CodeRay .inline {
            color: #000
        }

        .CodeRay .inline .inline {
            background: #ccc
        }

        .CodeRay .inline .inline .inline {
            background: #bbb
        }

        .CodeRay .inline .inline-delimiter {
            color: #d14
        }

        .CodeRay .inline-delimiter {
            color: #d14
        }

        .CodeRay .important {
            color: #555;
            font-weight: bold
        }

        .CodeRay .interpreted {
            color: #b2b
        }

        .CodeRay .instance-variable {
            color: #008080
        }

        .CodeRay .label {
            color: #970
        }

        .CodeRay .local-variable {
            color: #963
        }

        .CodeRay .octal {
            color: #40e
        }

        .CodeRay .predefined {
            color: #369
        }

        .CodeRay .preprocessor {
            color: #579
        }

        .CodeRay .pseudo-class {
            color: #555
        }

        .CodeRay .directive {
            font-weight: bold
        }

        .CodeRay .type {
            font-weight: bold
        }

        .CodeRay .predefined-type {
            color: inherit
        }

        .CodeRay .reserved, .CodeRay .keyword {
            color: #000;
            font-weight: bold
        }

        .CodeRay .key {
            color: #808
        }

        .CodeRay .key .delimiter {
            color: #606
        }

        .CodeRay .key .char {
            color: #80f
        }

        .CodeRay .value {
            color: #088
        }

        .CodeRay .regexp .delimiter {
            color: #808
        }

        .CodeRay .regexp .content {
            color: #808
        }

        .CodeRay .regexp .modifier {
            color: #808
        }

        .CodeRay .regexp .char {
            color: #d14
        }

        .CodeRay .regexp .function {
            color: #404;
            font-weight: bold
        }

        .CodeRay .string {
            color: #d20
        }

        .CodeRay .string .string .string {
            background: #ffd0d0
        }

        .CodeRay .string .content {
            color: #d14
        }

        .CodeRay .string .char {
            color: #d14
        }

        .CodeRay .string .delimiter {
            color: #d14
        }

        .CodeRay .shell {
            color: #d14
        }

        .CodeRay .shell .delimiter {
            color: #d14
        }

        .CodeRay .symbol {
            color: #990073
        }

        .CodeRay .symbol .content {
            color: #a60
        }

        .CodeRay .symbol .delimiter {
            color: #630
        }

        .CodeRay .tag {
            color: #008080
        }

        .CodeRay .tag-special {
            color: #d70
        }

        .CodeRay .variable {
            color: #036
        }

        .CodeRay .insert {
            background: #afa
        }

        .CodeRay .delete {
            background: #faa
        }

        .CodeRay .change {
            color: #aaf;
            background: #007
        }

        .CodeRay .head {
            color: #f8f;
            background: #505
        }

        .CodeRay .insert .insert {
            color: #080
        }

        .CodeRay .delete .delete {
            color: #800
        }

        .CodeRay .change .change {
            color: #66f
        }

        .CodeRay .head .head {
            color: #f4f
        }
    </style>
</head>
<body class="book toc2 toc-left">
<div id="header">
    <h1>Reactor 3 参考文档</h1>
    <div class="details">
        <span class="author" id="author">Stephane Maldini</span><br>
        <span class="email" id="email"><a href="https://twitter.com/smaldini">@smaldini</a></span><br>
        <span class="author" id="author2">Simon Baslé</span><br>
        <span class="email" id="email2"><a href="https://twitter.com/simonbasle">@simonbasle</a></span><br>
        <span id="revdate">3.2.0.BUILD-SNAPSHOT</span>
    </div>
    <div class="toc2" id="toc">
        <div id="toctitle">Table of Contents</div>
        <ul class="sectlevel1">
            <li><a href="#about-doc">1. 关于本文档</a>
                <ul class="sectlevel2">
                    <li><a href="#_最新版本_版权说明">1.1. 最新版本 &amp; 版权说明</a></li>
                    <li><a href="#_贡献本文档">1.2. 贡献本文档</a></li>
                    <li><a href="#_获取帮助">1.3. 获取帮助</a></li>
                    <li><a href="#_如何开始阅读本文档">1.4. 如何开始阅读本文档</a></li>
                </ul>
            </li>
            <li><a href="#getting-started">2. 快速上手</a>
                <ul class="sectlevel2">
                    <li><a href="#getting-started-introducing-reactor">2.1. 介绍 Reactor</a></li>
                    <li><a href="#prerequisites">2.2. 前提</a></li>
                    <li><a href="#getting-started-understanding-bom">2.3. 了解 BOM</a></li>
                    <li><a href="#getting">2.4. 获取 Reactor</a></li>
                </ul>
            </li>
            <li><a href="#intro-reactive">3. 响应式编程</a>
                <ul class="sectlevel2">
                    <li><a href="#_阻塞是对资源的浪费">3.1. 阻塞是对资源的浪费</a></li>
                    <li><a href="#_异步可以解决问题吗">3.2. 异步可以解决问题吗？</a></li>
                    <li><a href="#_从命令式编程到响应式编程">3.3. 从命令式编程到响应式编程</a></li>
                </ul>
            </li>
            <li><a href="#core-features">4. Reactor 核心特性</a>
                <ul class="sectlevel2">
                    <li><a href="#flux">4.1. <code>Flux</code>, 包含 0-N 个元素的异步序列</a></li>
                    <li><a href="#mono">4.2. <code>Mono</code>, 异步的 0-1 结果</a></li>
                    <li><a href="#_简单的创建和订阅_flux_或_mono_的方法">4.3. 简单的创建和订阅 Flux 或 Mono 的方法</a></li>
                    <li><a href="#producing">4.4. 可编程式地创建一个序列</a></li>
                    <li><a href="#schedulers">4.5. 调度器（Schedulers）</a></li>
                    <li><a href="#threading">4.6. 线程模型</a></li>
                    <li><a href="#error.handling">4.7. 处理错误</a></li>
                    <li><a href="#processors">4.8. Processors</a></li>
                </ul>
            </li>
            <li><a href="#kotlin">5. 对 Kotlin 的支持</a>
                <ul class="sectlevel2">
                    <li><a href="#kotlin-introduction">5.1. 简介</a></li>
                    <li><a href="#kotlin-requirements">5.2. 前提</a></li>
                    <li><a href="#kotlin-extensions">5.3. 扩展</a></li>
                    <li><a href="#kotlin-null-safety">5.4. Null 值安全</a></li>
                </ul>
            </li>
            <li><a href="#testing">6. 测试</a>
                <ul class="sectlevel2">
                    <li><a href="#_使用_code_stepverifier_code_来测试">6.1. 使用 <code>StepVerifier</code> 来测试</a></li>
                    <li><a href="#_操控时间">6.2. 操控时间</a></li>
                    <li><a href="#_使用_code_stepverifier_code_进行_后校验">6.3. 使用 <code>StepVerifier</code> 进行“后校验”</a></li>
                    <li><a href="#_测试_code_context_code">6.4. 测试 <code>Context</code></a></li>
                    <li><a href="#_用_code_testpublisher_code_手动发出元素">6.5. 用 <code>TestPublisher</code> 手动发出元素</a></li>
                    <li><a href="#_用_code_publisherprobe_code_检查执行路径">6.6. 用 <code>PublisherProbe</code> 检查执行路径</a></li>
                </ul>
            </li>
            <li><a href="#debugging">7. 调试 Reactor</a>
                <ul class="sectlevel2">
                    <li><a href="#_典型的_reactor_stack_trace">7.1. 典型的 Reactor Stack Trace</a></li>
                    <li><a href="#debug-activate">7.2. 开启调试模式</a></li>
                    <li><a href="#_阅读调试模式的_stack_trace">7.3. 阅读调试模式的 Stack Trace</a></li>
                    <li><a href="#_记录流的日志">7.4. 记录流的日志</a></li>
                </ul>
            </li>
            <li><a href="#advanced">8. 高级特性与概念</a>
                <ul class="sectlevel2">
                    <li><a href="#advanced-mutualizing-operator-usage">8.1. 打包重用操作符</a></li>
                    <li><a href="#reactor.hotCold">8.2. Hot vs Cold</a></li>
                    <li><a href="#advanced-broadcast-multiple-subscribers-connectableflux">8.3. 使用
                        <code>ConnectableFlux</code> 对多个订阅者进行广播</a></li>
                    <li><a href="#advanced-three-sorts-batching">8.4. 三种分批处理方式</a></li>
                    <li><a href="#advanced-parallelizing-parralelflux">8.5. 使用 <code>ParallelFlux</code> 进行并行处理</a></li>
                    <li><a href="#scheduler-factory">8.6. 替换默认的 <code>Schedulers</code></a></li>
                    <li><a href="#hooks">8.7. 使用全局的 Hooks</a></li>
                    <li><a href="#context">8.8. 增加一个 Context 到响应式序列</a></li>
                    <li><a href="#null-safety">8.9. 空值安全</a></li>
                </ul>
            </li>
            <li><a href="#which-operator">Appendix A: 我需要哪个操作符？</a>
                <ul class="sectlevel2">
                    <li><a href="#which.create">A.1. 创建一个新序列，它&#8230;&#8203;</a></li>
                    <li><a href="#which.values">A.2. 对序列进行转化</a></li>
                    <li><a href="#which.peeking">A.3. “窥视”（只读）序列</a></li>
                    <li><a href="#which.filtering">A.4. 过滤序列</a></li>
                    <li><a href="#which.errors">A.5. 错误处理</a></li>
                    <li><a href="#which.time">A.6. 基于时间的操作</a></li>
                    <li><a href="#which.window">A.7. 拆分 <code>Flux</code></a></li>
                    <li><a href="#which.blocking">A.8. 回到同步的世界</a></li>
                </ul>
            </li>
            <li><a href="#faq">Appendix B: FAQ，最佳实践，以及“我如何&#8230;&#8203;?”</a>
                <ul class="sectlevel2">
                    <li><a href="#faq.wrap-blocking">B.1. 如何包装一个同步阻塞的调用？</a></li>
                    <li><a href="#faq.chain">B.2. 用在 <code>Flux</code> 上的操作符好像没起作用，为啥？</a></li>
                    <li><a href="#faq.monoThen">B.3. <code>Mono</code> <code>zipWith</code>/<code>zipWhen</code>
                        没有被调用</a></li>
                    <li><a href="#faq.retryWhen">B.4. 如何用 <code>retryWhen</code> 来实现 <code>retry(3)</code> 的效果？</a></li>
                    <li><a href="#faq.exponentialBackoff">B.5. 如何使用 <code>retryWhen</code> 进行 exponential backoff？</a>
                    </li>
                    <li><a href="#_how_do_i_ensure_thread_affinity_using_code_publishon_code">B.6. How do I ensure
                        thread affinity using <code>publishOn()</code>?</a></li>
                </ul>
            </li>
            <li><a href="#reactor-extra">Appendix C: Reactor-Extra</a>
                <ul class="sectlevel2">
                    <li><a href="#extra-tuples">C.1. <code>TupleUtils</code> 以及函数式接口</a></li>
                    <li><a href="#extra-math">C.2. <code>MathFlux</code> 的数学操作符</a></li>
                    <li><a href="#extra-repeat-retry">C.3. 重复与重试工具</a></li>
                    <li><a href="#extra-schedulers">C.4. 调度器</a></li>
                </ul>
            </li>
        </ul>
    </div>
</div>
<div id="content">
    <div id="preamble">
        <div class="sectionbody">
            <div class="admonitionblock note">
                <table>
                    <tr>
                        <td class="icon">
                            <i class="fa icon-note" title="Note"></i>
                        </td>
                        <td class="content">
                            <div class="paragraph">
                                <p>（译者加）本文档的一些典型的名词如下：</p>
                            </div>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>
                                            <code>Publisher</code>（发布者）、<code>Subscriber</code>（订阅者）、<code>Subscription</code>（订阅
                                            n.）、<code>subscribe</code>（订阅 v.）。</p>
                                    </li>
                                    <li>
                                        <p><code>event</code>/<code>signal</code>（事件/信号，原文常常在一个句子将两个词来回用，但表示的意思是基本相同的，
                                            因此如果你看到本文翻译有时候用事件，有时候用信号，在本文档内基本可以认为一个意思）。</p>
                                    </li>
                                    <li>
                                        <p><code>sequence</code>/<code>stream</code>（序列/流，两个词意思相似，本文介绍的是响应式流的内容，但是出现比较多的是
                                            sequence这个词，主要翻译为“序列”，有些地方为了更加契合且方便理解翻译为“流序列”）。</p>
                                    </li>
                                    <li>
                                        <p><code>element</code>/<code>item</code>（主要指序列中的元素，文中两个词基本翻译为“元素”）。</p>
                                    </li>
                                    <li>
                                        <p><code>emit</code>/<code>produce</code>/<code>generate</code>（发出/产生/生成，文中这三个英文词也有相似之处，对于
                                            emit 多翻译为
                                            “发出”，对于后两个多翻译为“生成”）、<code>consume</code>（消费）。</p>
                                    </li>
                                    <li>
                                        <p><code>Processor</code>（未做翻译，保留英文）。</p>
                                    </li>
                                    <li>
                                        <p><code>operator</code>（译作操作符，声明式的可组装的响应式方法，其组装成的链译作“操作链”）。</p>
                                    </li>
                                </ul>
                            </div>
                        </td>
                    </tr>
                </table>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="about-doc"><a class="anchor" href="#about-doc"></a>1. 关于本文档</h2>
        <div class="sectionbody">
            <div class="paragraph">
                <p>本节是对 Reactor参考文档（译者加：原文估计是多个人写的，时而“document”时而“guide”，不影响理解的情况下，
                    翻译就一律用“文档”了） 的简要概述。你并不需要从头到尾阅读该文档。每一节的内容都是独立的，不过会有其他章节的链接。</p>
            </div>
            <div class="sect2">
                <h3 id="_最新版本_版权说明"><a class="anchor" href="#_最新版本_版权说明"></a>1.1. 最新版本 &amp; 版权说明</h3>
                <div class="paragraph">
                    <p>本Reactor参考文档也提供HTML形式。最新版本见 <a class="bare"
                                                      href="http://projectreactor.io/docs/core/release/reference/docs/index.html。">http://projectreactor.io/docs/core/release/reference/docs/index.html。</a>
                    </p>
                </div>
                <div class="paragraph">
                    <p>本文档的副本你可以自用，亦可分发给他人。不过无论是打印版还是电子版，请免费提供。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_贡献本文档"><a class="anchor" href="#_贡献本文档"></a>1.2. 贡献本文档</h3>
                <div class="paragraph">
                    <p>本参考文档用 <a href="http://asciidoctor.org/docs/asciidoc-writers-guide/">Asciidoc</a> 编写，
                        其源码见 <a class="bare"
                                href="https://github.com/reactor/reactor-core/tree/master/src/docs/asciidoc">https://github.com/reactor/reactor-core/tree/master/src/docs/asciidoc</a>
                        （译者加：本翻译源码见 <a class="bare"
                                       href="https://github.com/get-set/reactor-core/tree/master-zh/src/docs/asciidoc">https://github.com/get-set/reactor-core/tree/master-zh/src/docs/asciidoc</a>
                        ）。</p>
                </div>
                <div class="paragraph">
                    <p>如有任何补充，欢迎你提交 pull request。</p>
                </div>
                <div class="paragraph">
                    <p>我们建议你将源码 checkout 到本地，这样可以使用 gradle 的 <code>asciidoctor</code> 任务检查文档渲染效果。
                        有些章节会包含其他文件，Github 并不一定能够渲染出来。</p>
                </div>
                <div class="admonitionblock tip">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-tip" title="Tip"></i>
                            </td>
                            <td class="content">
                                为了方便读者的反馈，多数章节在结尾都提供一个链接，这个链接可以打开一个 Github 上的
                                编辑界面，从而可以编辑相应章节的源码。这些链接在 HTML5 的版本中能够看到，就像这样：
                                <a class="fa fa-edit"
                                   href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/aboutDoc.adoc"
                                   rel="noopener" target="_blank">翻译建议</a> - <a href="#about-doc">关于本文档</a>。
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_获取帮助"><a class="anchor" href="#_获取帮助"></a>1.3. 获取帮助</h3>
                <div class="paragraph">
                    <p>Reactor项目有多种方式希望能帮助到你：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>与社区沟通： <a href="https://gitter.im/reactor/reactor">Gitter</a>。</p>
                        </li>
                        <li>
                            <p>在 stackoverflow.com 的 <a href="http://stackoverflow.com/tags/project-reactor"><code>project-reactor</code></a>
                                进行提问。</p>
                        </li>
                        <li>
                            <p>在 Github issues 提交 bug 。下边这几个库我们会一直关注：
                                <a href="http://github.com/reactor/reactor-core/issues">reactor-core</a> （涉及 Reactor
                                的核心功能）
                                以及 <a href="http://github.com/reactor/reactor-addons/issues">reactor-addons</a>
                                （涉及 reactor-test 和 adapters issues）。</p>
                        </li>
                    </ul>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                所有 Reactor 项目都是开源的，
                                <a href="https://github.com/reactor/reactor-core/tree/master/src/docs/asciidoc">包括本文档</a>。
                                如果你发现本文档有问题，或希望补充一些内容，请参考
                                <a href="https://github.com/reactor/reactor-core/blob/master/CONTRIBUTING.md">这里</a>
                                进行了解。
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_如何开始阅读本文档"><a class="anchor" href="#_如何开始阅读本文档"></a>1.4. 如何开始阅读本文档</h3>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>如果你想直接写代码请参考 <a href="#getting-started">快速上手</a> 。</p>
                        </li>
                        <li>
                            <p>如果你对 <em>响应式编程（Reactive Programming）</em> 比较陌生，最好从 <a href="#intro-reactive">响应式编程</a>
                                开始。</p>
                        </li>
                        <li>
                            <p>如果你对 Reactor 的理念比较熟悉，只是在编写程序时查找合适的操作符， 请参考附录
                                <a href="#which-operator">我需要哪个操作符？</a> 。</p>
                        </li>
                        <li>
                            <p>如果你想深入了解 Reactor 的核心功能，请参考 <a href="#core-features">Reactor 核心特性</a>，以便了解：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>关于 Reactor 的响应式类型 "<a href="#flux"><code>Flux</code>, 包含 0-N 个元素的异步序列</a>" 和
                                            "<a href="#mono"><code>Mono</code>, 异步的 0-1 结果</a>"；</p>
                                    </li>
                                    <li>
                                        <p>如何调整执行的线程环境： <a href="#schedulers">调度器</a>；</p>
                                    </li>
                                    <li>
                                        <p>如何处理问题： <a href="#error.handling">处理错误</a>。</p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>单测的内容主要来自 <code>reactor-test</code> 项目，参考 <a href="#testing">测试</a>。</p>
                        </li>
                        <li>
                            <p><a href="#producing">可编程式地创建一个序列</a> 提供了更加丰富的创建响应式源（reactive source）的方式。</p>
                        </li>
                        <li>
                            <p>其他高级主题请看参考 <a href="#advanced">高级特性与概念</a>。</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p><a class="fa fa-edit"
                          href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/aboutDoc.adoc"
                          rel="noopener" target="_blank" title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                        - "<a href="#about-doc">关于本文档</a>"</p>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="getting-started"><a class="anchor" href="#getting-started"></a>2. 快速上手</h2>
        <div class="sectionbody">
            <div class="paragraph">
                <p>这一节的内容能够帮助你上手使用 Reactor。包括如下内容：</p>
            </div>
            <div class="ulist">
                <ul>
                    <li>
                        <p><a href="#getting-started-introducing-reactor">介绍 Reactor</a></p>
                    </li>
                    <li>
                        <p><a href="#prerequisites">前提</a></p>
                    </li>
                    <li>
                        <p><a href="#getting-started-understanding-bom">了解 BOM</a></p>
                    </li>
                    <li>
                        <p><a href="#getting">获取 Reactor</a></p>
                    </li>
                </ul>
            </div>
            <div class="sect2">
                <h3 id="getting-started-introducing-reactor"><a class="anchor"
                                                                href="#getting-started-introducing-reactor"></a>2.1. 介绍
                    Reactor</h3>
                <div class="paragraph">
                    <p>Reactor 是一个用于JVM的完全非阻塞的响应式编程框架，具备高效的需求管理（即对
                        “背压（backpressure）”的控制）能力。它与 Java 8 函数式 API 直接集成，比如 <code>CompletableFuture</code>，
                        <code>Stream</code>， 以及 <code>Duration</code>。它提供了异步序列 API <code>Flux</code>（用于[N]个元素）和 <code>Mono</code>（用于
                        [0|1]个元素），并完全遵循和实现了“响应式扩展规范”（Reactive Extensions Specification）。</p>
                </div>
                <div class="paragraph">
                    <p>Reactor 的 <code>reactor-ipc</code> 组件还支持非阻塞的进程间通信（inter-process communication, IPC）。
                        Reactor IPC 为 HTTP（包括 Websockets）、TCP 和 UDP 提供了支持背压的网络引擎，从而适合
                        应用于微服务架构。并且完整支持响应式编解码（reactive encoding and decoding）。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="prerequisites"><a class="anchor" href="#prerequisites"></a>2.2. 前提</h3>
                <div class="paragraph">
                    <p>Reactor Core 运行于 <code>Java 8</code> 及以上版本。</p>
                </div>
                <div class="paragraph">
                    <p>依赖 <code>org.reactive-streams:reactive-streams:1.0.2</code>。</p>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                <div class="paragraph">
                                    <p><strong>Andriod 支持方面</strong>:</p>
                                </div>
                                <div class="ulist">
                                    <ul>
                                        <li>
                                            <p>Reactor 3 并不正式支持 Andorid（如果需要可以考虑使用 RxJava 2）。</p>
                                        </li>
                                        <li>
                                            <p>但是，在 Android SDK 26（Android 0）及以上版本应该没问题。</p>
                                        </li>
                                        <li>
                                            <p>我们希望能够最大程度兼顾对 Android 的支持，但是我们并不能作出保证，具体情况具体分析。</p>
                                        </li>
                                    </ul>
                                </div>
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="getting-started-understanding-bom"><a class="anchor"
                                                              href="#getting-started-understanding-bom"></a>2.3. 了解 BOM
                </h3>
                <div class="paragraph">
                    <p>自从 <code>reactor-core 3.0.4</code>，随着 <code>Aluminium</code> 版本发布上车（release train）以来，Reactor 3
                        使用了 BOM（Bill of Materials，一种标准的 Maven artifact）。</p>
                </div>
                <div class="paragraph">
                    <p>使用 BOM 可以管理一组良好集成的 maven artifacts，从而无需操心不同版本组件的互相依赖问题。</p>
                </div>
                <div class="paragraph">
                    <p>BOM 是一系列有版本信息的 artifacts，通过“列车发布”（release train）的发布方式管理，
                        每趟发布列车由一个“代号+修饰词”组成，比如：</p>
                </div>
                <div class="verseblock">
<pre class="content">Aluminium-RELEASE
Carbon-BUILD-SNAPSHOT
Aluminium-SR1
Bismuth-RELEASE
Carbon-SR32</pre>
                </div>
                <div class="paragraph">
                    <p>代号替代了传统的“主版本.次版本”的数字形式。这些代号主要来自
                        <a href="https://en.wikipedia.org/wiki/Periodic_table#Overview">Periodic Table of
                            Elements</a>， 按首字母顺序依次选取。</p>
                </div>
                <div class="paragraph">
                    <p>修饰词有（按照时间顺序）：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><code>BUILD-SNAPSHOT</code></p>
                        </li>
                        <li>
                            <p><code>M1</code>..<code>N</code>: 里程碑号</p>
                        </li>
                        <li>
                            <p><code>RELEASE</code>: 第一次 GA (General Availability) 发布</p>
                        </li>
                        <li>
                            <p><code>SR1</code>..<code>N</code>: 后续的 GA 发布（类似于 PATCH 号或 SR（Service Release））。</p>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="sect2">
                <h3 id="getting"><a class="anchor" href="#getting"></a>2.4. 获取 Reactor</h3>
                <div class="paragraph">
                    <p>前边提到，使用 Reactor 的最简单方式是在你的项目中配置 BOM 以及相关依赖。
                        注意，当你这样添加依赖的时候，要省略版本（&lt;version&gt;）配置，从而自动使用 BOM 中指定的版本。</p>
                </div>
                <div class="paragraph">
                    <p>当然，如果你希望使用某个版本的 artifact，仍然可以指定。甚至完全不使用 BOM，逐个配置
                        artifact 的版本也是可以的。</p>
                </div>
                <div class="sect3">
                    <h4 id="_maven_配置"><a class="anchor" href="#_maven_配置"></a>2.4.1. Maven 配置</h4>
                    <div class="paragraph">
                        <p>Maven 原生支持 BOM。首先，你需要在 <code>pom.xml</code> 内通过添加下边的代码引入 BOM。如果
                            (<code>dependencyManagement</code>) 已经存在，只需要添加其内容即可。</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="xml"><span class="tag">&lt;dependencyManagement&gt;</span> <i
        class="conum" data-value="1"></i><b>(1)</b>
    <span class="tag">&lt;dependencies&gt;</span>
        <span class="tag">&lt;dependency&gt;</span>
            <span class="tag">&lt;groupId&gt;</span>io.projectreactor<span class="tag">&lt;/groupId&gt;</span>
            <span class="tag">&lt;artifactId&gt;</span>reactor-bom<span class="tag">&lt;/artifactId&gt;</span>
            <span class="tag">&lt;version&gt;</span>Bismuth-RELEASE<span class="tag">&lt;/version&gt;</span>
            <span class="tag">&lt;type&gt;</span>pom<span class="tag">&lt;/type&gt;</span>
            <span class="tag">&lt;scope&gt;</span>import<span class="tag">&lt;/scope&gt;</span>
        <span class="tag">&lt;/dependency&gt;</span>
    <span class="tag">&lt;/dependencies&gt;</span>
<span class="tag">&lt;/dependencyManagement&gt;</span></code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>注意 <code>dependencyManagement</code> 标签用来补充通常使用的 <code>dependencies</code> 配置。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>然后，在 <code>dependencies</code> 中添加相关的 reactor 项目，省略 <code>&lt;version&gt;</code>，如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="xml"><span class="tag">&lt;dependencies&gt;</span>
    <span class="tag">&lt;dependency&gt;</span>
        <span class="tag">&lt;groupId&gt;</span>io.projectreactor<span class="tag">&lt;/groupId&gt;</span>
        <span class="tag">&lt;artifactId&gt;</span>reactor-core<span class="tag">&lt;/artifactId&gt;</span> <i
            class="conum" data-value="1"></i><b>(1)</b>
        <i class="conum" data-value="2"></i><b>(2)</b>
    <span class="tag">&lt;/dependency&gt;</span>
    <span class="tag">&lt;dependency&gt;</span>
        <span class="tag">&lt;groupId&gt;</span>io.projectreactor<span class="tag">&lt;/groupId&gt;</span>
        <span class="tag">&lt;artifactId&gt;</span>reactor-test<span class="tag">&lt;/artifactId&gt;</span> <i
            class="conum" data-value="3"></i><b>(3)</b>
        <span class="tag">&lt;scope&gt;</span>test<span class="tag">&lt;/scope&gt;</span>
    <span class="tag">&lt;/dependency&gt;</span>
<span class="tag">&lt;/dependencies&gt;</span></code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>依赖 Core 库</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>没有 version 标签</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td><code>reactor-test</code> 提供了对 reactive streams 的单测</td>
                            </tr>
                        </table>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_gradle_配置"><a class="anchor" href="#_gradle_配置"></a>2.4.2. Gradle 配置</h4>
                    <div class="paragraph">
                        <p>Gradle 没有对 Maven BOM 的支持，但是你可以使用 Spring 的
                            <a href="https://github.com/spring-gradle-plugins/dependency-management-plugin">gradle-dependency-management</a>
                            插件。</p>
                    </div>
                    <div class="paragraph">
                        <p>首先，apply 插件。</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="groovy">plugins {
    id <span class="string"><span class="delimiter">&quot;</span><span
            class="content">io.spring.dependency-management</span><span
            class="delimiter">&quot;</span></span> version <span class="string"><span
            class="delimiter">&quot;</span><span class="content">1.0.1.RELEASE</span><span
            class="delimiter">&quot;</span></span> <i class="conum" data-value="1"></i><b>(1)</b>
}</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>编写本文档时，插件最新版本为 1.0.1.RELEASE，请自行使用合适的版本。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>然后用它引入 BOM：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="groovy">dependencyManagement {
     imports {
          mavenBom <span class="string"><span class="delimiter">&quot;</span><span class="content">io.projectreactor:reactor-bom:Bismuth-RELEASE</span><span
            class="delimiter">&quot;</span></span>
     }
}</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>Finally add a dependency to your project, without a version number:</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="groovy">dependencies {
     compile <span class="string"><span class="delimiter">'</span><span
            class="content">io.projectreactor:reactor-core</span><span class="delimiter">'</span></span> <i
            class="conum" data-value="1"></i><b>(1)</b>
}</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>无需第三个 <code>:</code> 添加版本号。</td>
                            </tr>
                        </table>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_milestones_和_snapshots"><a class="anchor" href="#_milestones_和_snapshots"></a>2.4.3.
                        Milestones 和 Snapshots</h4>
                    <div class="paragraph">
                        <p>里程碑版（Milestones）和开发预览版（developer previews）通过 Spring Milestones
                            repository 而不是 Maven Central 来发布。 需要添加到构建配置文件中，如：</p>
                    </div>
                    <div class="listingblock">
                        <div class="title">Milestones in Maven</div>
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="xml"><span class="tag">&lt;repositories&gt;</span>
        <span class="tag">&lt;repository&gt;</span>
                <span class="tag">&lt;id&gt;</span>spring-milestones<span class="tag">&lt;/id&gt;</span>
                <span class="tag">&lt;name&gt;</span>Spring Milestones Repository<span class="tag">&lt;/name&gt;</span>
                <span class="tag">&lt;url&gt;</span>https://repo.spring.io/milestone<span
            class="tag">&lt;/url&gt;</span>
        <span class="tag">&lt;/repository&gt;</span>
<span class="tag">&lt;/repositories&gt;</span></code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>gradle 使用下边的配置：</p>
                    </div>
                    <div class="listingblock">
                        <div class="title">Milestones in Gradle</div>
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="groovy">repositories {
  maven { url <span class="string"><span class="delimiter">'</span><span
            class="content">http://repo.spring.io/milestone</span><span class="delimiter">'</span></span> }
  mavenCentral()
}</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>类似的，snapshot 版也需要配置专门的库：</p>
                    </div>
                    <div class="listingblock">
                        <div class="title">BUILD-SNAPSHOTs in Maven</div>
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="xml"><span class="tag">&lt;repositories&gt;</span>
        <span class="tag">&lt;repository&gt;</span>
                <span class="tag">&lt;id&gt;</span>spring-snapshots<span class="tag">&lt;/id&gt;</span>
                <span class="tag">&lt;name&gt;</span>Spring Snapshot Repository<span class="tag">&lt;/name&gt;</span>
                <span class="tag">&lt;url&gt;</span>https://repo.spring.io/snapshot<span class="tag">&lt;/url&gt;</span>
        <span class="tag">&lt;/repository&gt;</span>
<span class="tag">&lt;/repositories&gt;</span></code></pre>
                        </div>
                    </div>
                    <div class="listingblock">
                        <div class="title">BUILD-SNAPSHOTs in Gradle</div>
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="groovy">repositories {
  maven { url <span class="string"><span class="delimiter">'</span><span
            class="content">http://repo.spring.io/snapshot</span><span class="delimiter">'</span></span> }
  mavenCentral()
}</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p><a class="fa fa-edit"
                              href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/gettingStarted.adoc"
                              title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                            - "<a href="#getting-started">快速上手</a>"</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="intro-reactive"><a class="anchor" href="#intro-reactive"></a>3. 响应式编程</h2>
        <div class="sectionbody">
            <div class="paragraph">
                <p>Reactor 是响应式编程范式的实现，总结起来有如下几点：</p>
            </div>
            <div class="paragraph">
                <p>响应式编程是一种关注于数据流（data streams）和变化传递（propagation of change）的异步编程方式。
                    这意味着它可以用既有的编程语言表达静态（如数组）或动态（如事件源）的数据流。</p>
            </div>
            <div class="paragraph">
                <p>在响应式编程方面，微软跨出了第一步，它在 .NET 生态中创建了响应式扩展库（Reactive Extensions
                    library, Rx）。接着 RxJava 在JVM上实现了响应式编程。后来，在 JVM 平台出现了一套标准的响应式
                    编程规范，它定义了一系列标准接口和交互规范。并整合到 Java 9 中（使用 <code>Flow</code> 类）。</p>
            </div>
            <div class="paragraph">
                <p>响应式编程通常作为面向对象编程中的“观察者模式”（Observer design pattern）的一种扩展。
                    响应式流（reactive streams）与“迭代子模式”（Iterator design pattern）也有相通之处，
                    因为其中也有 <code>Iterable</code>-<code>Iterator</code> 这样的对应关系。主要的区别在于，Iterator 是基于
                    “拉取”（pull）方式的，而响应式流是基于“推送”（push）方式的。</p>
            </div>
            <div class="paragraph">
                <p>使用 iterator 是一种“命令式”（imperative）编程范式，即使访问元素的方法是 <code>Iterable</code>
                    的唯一职责。关键在于，什么时候执行 <code>next()</code> 获取元素取决于开发者。在响应式流中，相对应的
                    角色是 <code>Publisher-Subscriber</code>，但是 <em>当有新的值到来的时候</em> ，却反过来由发布者（Publisher）
                    通知订阅者（Subscriber），这种“推送”模式是响应式的关键。此外，对推送来的数据的操作
                    是通过一种声明式（declaratively）而不是命令式（imperatively）的方式表达的：开发者通过
                    描述“控制流程”来定义对数据流的处理逻辑。</p>
            </div>
            <div class="paragraph">
                <p>除了数据推送，对错误处理（error handling）和完成（completion）信号的定义也很完善。
                    一个 <code>Publisher</code> 可以推送新的值到它的 <code>Subscriber</code>（调用 <code>onNext</code> 方法），
                    同样也可以推送错误（调用 <code>onError</code> 方法）和完成（调用 <code>onComplete</code> 方法）信号。
                    错误和完成信号都可以终止响应式流。可以用下边的表达式描述：</p>
            </div>
            <div class="listingblock">
                <div class="content">
                    <pre class="CodeRay highlight"><code>onNext x 0..N [onError | onComplete]</code></pre>
                </div>
            </div>
            <div class="paragraph">
                <p>这种方式非常灵活，无论是有/没有值，还是 n 个值（包括有无限个值的流，比如时钟的持续读秒），都可处理。</p>
            </div>
            <div class="paragraph">
                <p>那么我们为什么需要这样的异步响应式开发库呢？</p>
            </div>
            <div class="sect2">
                <h3 id="_阻塞是对资源的浪费"><a class="anchor" href="#_阻塞是对资源的浪费"></a>3.1. 阻塞是对资源的浪费</h3>
                <div class="paragraph">
                    <p>现代应用需要应对大量的并发用户，而且即使现代硬件的处理能力飞速发展，软件性能仍然是关键因素。</p>
                </div>
                <div class="paragraph">
                    <p>广义来说我们有两种思路来提升程序性能：</p>
                </div>
                <div class="olist arabic">
                    <ol class="arabic">
                        <li>
                            <p><strong>并行化（parallelize）</strong> ：使用更多的线程和硬件资源。</p>
                        </li>
                        <li>
                            <p>基于现有的资源来 <strong>提高执行效率</strong> 。</p>
                        </li>
                    </ol>
                </div>
                <div class="paragraph">
                    <p>通常，Java开发者使用阻塞式（blocking）编写代码。这没有问题，在出现性能瓶颈后，
                        我们可以增加处理线程，线程中同样是阻塞的代码。但是这种使用资源的方式会迅速面临
                        资源竞争和并发问题。</p>
                </div>
                <div class="paragraph">
                    <p>更糟糕的是，阻塞会浪费资源。具体来说，比如当一个程序面临延迟（通常是I/O方面，
                        比如数据库读写请求或网络调用），所在线程需要进入 idle 状态等待数据，从而浪费资源。</p>
                </div>
                <div class="paragraph">
                    <p>所以，并行化方式并非银弹。这是挖掘硬件潜力的方式，但是却带来了复杂性，而且容易造成浪费。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_异步可以解决问题吗"><a class="anchor" href="#_异步可以解决问题吗"></a>3.2. 异步可以解决问题吗？</h3>
                <div class="paragraph">
                    <p>第二种思路——提高执行效率——可以解决资源浪费问题。通过编写 <em>异步非阻塞</em> 的代码，
                        （任务发起异步调用后）执行过程会切换到另一个 <strong>使用同样底层资源</strong> 的活跃任务，然后等
                        异步调用返回结果再去处理。</p>
                </div>
                <div class="paragraph">
                    <p>但是在 JVM 上如何编写异步代码呢？Java 提供了两种异步编程方式：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><strong>回调（Callbacks）</strong> ：异步方法没有返回值，而是采用一个 <code>callback</code> 作为参数（lambda
                                或匿名类），当结果出来后回调这个 <code>callback</code>。常见的例子比如 Swings 的 <code>EventListener</code>。</p>
                        </li>
                        <li>
                            <p><strong>Futures</strong> ：异步方法 <strong>立即</strong> 返回一个 <code>Future&lt;T&gt;</code>，该异步方法要返回结果的是
                                <code>T</code> 类型，通过
                                <code>Future`封装。这个结果并不是 *立刻* 可以拿到，而是等实际处理结束才可用。比如， `ExecutorService</code>
                                执行 <code>Callable&lt;T&gt;</code> 任务时会返回 <code>Future</code> 对象。</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p>这些技术够用吗？并非对于每个用例都是如此，两种方式都有局限性。</p>
                </div>
                <div class="paragraph">
                    <p>回调很难组合起来，因为很快就会导致代码难以理解和维护（即所谓的“回调地狱（callback hell）”）。</p>
                </div>
                <div class="paragraph">
                    <p>考虑这样一种情景：在用户界面上显示用户的5个收藏，或者如果没有任何收藏提供5个建议。这需要3个
                        服务（一个提供收藏的ID列表，第二个服务获取收藏内容，第三个提供建议内容）：</p>
                </div>
                <div class="listingblock">
                    <div class="title">回调地狱（Callback Hell）的例子</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">userService.getFavorites(userId, <span class="keyword">new</span> <span
        class="predefined-type">Callback</span>&lt;<span class="predefined-type">List</span>&lt;<span
        class="predefined-type">String</span>&gt;&gt;() { <i class="conum" data-value="1"></i><b>(1)</b>
  <span class="directive">public</span> <span class="type">void</span> onSuccess(<span
            class="predefined-type">List</span>&lt;<span class="predefined-type">String</span>&gt; list) { <i
            class="conum" data-value="2"></i><b>(2)</b>
    <span class="keyword">if</span> (list.isEmpty()) { <i class="conum" data-value="3"></i><b>(3)</b>
      suggestionService.getSuggestions(<span class="keyword">new</span> <span class="predefined-type">Callback</span>&lt;<span
            class="predefined-type">List</span>&lt;Favorite&gt;&gt;() {
        <span class="directive">public</span> <span class="type">void</span> onSuccess(<span class="predefined-type">List</span>&lt;Favorite&gt; list) { <i
            class="conum" data-value="4"></i><b>(4)</b>
          UiUtils.submitOnUiThread(() -&gt; { <i class="conum" data-value="5"></i><b>(5)</b>
            list.stream()
                .limit(<span class="integer">5</span>)
                .forEach(uiList::show); <i class="conum" data-value="6"></i><b>(6)</b>
            });
        }

        <span class="directive">public</span> <span class="type">void</span> onError(<span class="predefined-type">Throwable</span> error) { <i
            class="conum" data-value="7"></i><b>(7)</b>
          UiUtils.errorPopup(error);
        }
      });
    } <span class="keyword">else</span> {
      list.stream() <i class="conum" data-value="8"></i><b>(8)</b>
          .limit(<span class="integer">5</span>)
          .forEach(favId -&gt; favoriteService.getDetails(favId, <i class="conum" data-value="9"></i><b>(9)</b>
            <span class="keyword">new</span> <span class="predefined-type">Callback</span>&lt;Favorite&gt;() {
              <span class="directive">public</span> <span class="type">void</span> onSuccess(Favorite details) {
                UiUtils.submitOnUiThread(() -&gt; uiList.show(details));
              }

              <span class="directive">public</span> <span class="type">void</span> onError(<span
            class="predefined-type">Throwable</span> error) {
                UiUtils.errorPopup(error);
              }
            }
          ));
    }
  }

  <span class="directive">public</span> <span class="type">void</span> onError(<span
            class="predefined-type">Throwable</span> error) {
    UiUtils.errorPopup(error);
  }
});</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>基于回调的服务使用一个匿名 <code>Callback</code> 作为参数。后者的两个方法分别在异步执行成功
                                或异常时被调用。
                            </td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>获取到收藏ID的list后调用第一个服务的回调方法 <code>onSuccess</code>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>如果 list 为空， 调用 <code>suggestionService</code>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>服务 <code>suggestionService</code> 传递 <code>List&lt;Favorite&gt;</code> 给第二个回调。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="5"></i><b>5</b></td>
                            <td>既然是处理 UI，我们需要确保消费代码运行在 UI 线程。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="6"></i><b>6</b></td>
                            <td>使用 Java 8 <code>Stream</code> 来限制建议数量为5，然后在 UI 中显示。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="7"></i><b>7</b></td>
                            <td>在每一层，我们都以同样的方式处理错误：在一个 popup 中显示错误信息。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="8"></i><b>8</b></td>
                            <td>回到收藏 ID 这一层，如果返回 list，我们需要使用 <code>favoriteService</code> 来获取 <code>Favorite</code>
                                对象。由于只想要5个，因此使用 stream 。
                            </td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="9"></i><b>9</b></td>
                            <td>再一次回调。这次对每个ID，获取 <code>Favorite</code> 对象在 UI 线程中推送到前端显示。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>这里有不少代码，稍微有些难以阅读，并且还有重复代码，我们再来看一下用 Reactor 实现同样功能：</p>
                </div>
                <div class="listingblock">
                    <div class="title">使用 Reactor 实现以上回调方式同样功能的例子</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">userService.getFavorites(userId) <i class="conum"
                                                                                          data-value="1"></i><b>(1)</b>
           .flatMap(favoriteService::getDetails) <i class="conum" data-value="2"></i><b>(2)</b>
           .switchIfEmpty(suggestionService.getSuggestions()) <i class="conum" data-value="3"></i><b>(3)</b>
           .take(<span class="integer">5</span>) <i class="conum" data-value="4"></i><b>(4)</b>
           .publishOn(UiUtils.uiThreadScheduler()) <i class="conum" data-value="5"></i><b>(5)</b>
           .subscribe(uiList::show, UiUtils::errorPopup); <i class="conum" data-value="6"></i><b>(6)</b></code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>我们获取到收藏ID的流</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>我们 <em>异步地转换</em> 它们（ID） 为 <code>Favorite</code> 对象（使用 <code>flatMap</code>），现在我们有了
                                `Favorite`流。
                            </td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>一旦 <code>Favorite</code> 为空，切换到 <code>suggestionService</code>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>我们只关注流中的最多5个元素。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="5"></i><b>5</b></td>
                            <td>最后，我们希望在 UI 线程中进行处理。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="6"></i><b>6</b></td>
                            <td>通过描述对数据的最终处理（在 UI 中显示）和对错误的处理（显示在 popup 中）来触发（<code>subscribe</code>）。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>如果你想确保“收藏的ID”的数据在800ms内获得（如果超时，从缓存中获取）呢？在基于回调的代码中，
                        会比较复杂。但 Reactor 中就很简单，在处理链中增加一个 <code>timeout</code> 的操作符即可。</p>
                </div>
                <div class="listingblock">
                    <div class="title">Reactor 中增加超时控制的例子</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">userService.getFavorites(userId)
           .timeout(<span class="predefined-type">Duration</span>.ofMillis(<span class="integer">800</span>)) <i
            class="conum" data-value="1"></i><b>(1)</b>
           .onErrorResume(cacheService.cachedFavoritesFor(userId)) <i class="conum" data-value="2"></i><b>(2)</b>
           .flatMap(favoriteService::getDetails) <i class="conum" data-value="3"></i><b>(3)</b>
           .switchIfEmpty(suggestionService.getSuggestions())
           .take(<span class="integer">5</span>)
           .publishOn(UiUtils.uiThreadScheduler())
           .subscribe(uiList::show, UiUtils::errorPopup);</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>如果流在超时时限没有发出（emit）任何值，则发出错误（error）。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>一旦收到错误，交由 <code>cacheService</code> 处理。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>处理链后边的内容与上例类似。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>Futures 比回调要好一点，但即使在 Java 8 引入了 <code>CompletableFuture</code>，它对于多个处理的组合仍不够好用。
                        编排多个 Futures 是可行的，但却不易。此外，<code>Future</code> 还有一个问题：当对 <code>Future</code> 对象最终调用
                        <code>get()</code> 方法时，仍然会导致阻塞，并且缺乏对多个值以及更进一步对错误的处理。</p>
                </div>
                <div class="paragraph">
                    <p>考虑另外一个例子，我们首先得到 ID 的列表，然后通过它进一步获取到“对应的 name 和 statistics”
                        为元素的列表，整个过程用异步方式来实现。</p>
                </div>
                <div class="listingblock">
                    <div class="title"><code>CompletableFuture</code> 处理组合的例子</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">CompletableFuture&lt;<span class="predefined-type">List</span>&lt;<span
        class="predefined-type">String</span>&gt;&gt; ids = ifhIds(); <i class="conum" data-value="1"></i><b>(1)</b>

CompletableFuture&lt;<span class="predefined-type">List</span>&lt;<span class="predefined-type">String</span>&gt;&gt; result = ids.thenComposeAsync(l -&gt; { <i
            class="conum" data-value="2"></i><b>(2)</b>
        Stream&lt;CompletableFuture&lt;<span class="predefined-type">String</span>&gt;&gt; zip =
                        l.stream().map(i -&gt; { <i class="conum" data-value="3"></i><b>(3)</b>
                                                 CompletableFuture&lt;<span class="predefined-type">String</span>&gt; nameTask = ifhName(i); <i
            class="conum" data-value="4"></i><b>(4)</b>
                                                 CompletableFuture&lt;<span class="predefined-type">Integer</span>&gt; statTask = ifhStat(i); <i
            class="conum" data-value="5"></i><b>(5)</b>

                                                 <span class="keyword">return</span> nameTask.thenCombineAsync(statTask, (name, stat) -&gt; <span
            class="string"><span class="delimiter">&quot;</span><span class="content">Name </span><span
            class="delimiter">&quot;</span></span> + name + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> has stats </span><span class="delimiter">&quot;</span></span> + stat); <i
            class="conum" data-value="6"></i><b>(6)</b>
                                         });
        <span class="predefined-type">List</span>&lt;CompletableFuture&lt;<span class="predefined-type">String</span>&gt;&gt; combinationList = zip.collect(Collectors.toList()); <i
            class="conum" data-value="7"></i><b>(7)</b>
        CompletableFuture&lt;<span class="predefined-type">String</span>&gt;<span class="type">[]</span> combinationArray = combinationList.toArray(<span
            class="keyword">new</span> CompletableFuture[combinationList.size()]);

        CompletableFuture&lt;<span class="predefined-type">Void</span>&gt; allDone = CompletableFuture.allOf(combinationArray); <i
            class="conum" data-value="8"></i><b>(8)</b>
        <span class="keyword">return</span> allDone.thenApply(v -&gt; combinationList.stream()
                                                                                                 .map(CompletableFuture::join) <i
            class="conum" data-value="9"></i><b>(9)</b>
                                                                                                 .collect(Collectors.toList()));
});

<span class="predefined-type">List</span>&lt;<span class="predefined-type">String</span>&gt; results = result.join(); <i
            class="conum" data-value="10"></i><b>(10)</b>
assertThat(results).contains(
                                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameJoe has stats 103</span><span
                                        class="delimiter">&quot;</span></span>,
                                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameBart has stats 104</span><span
                                        class="delimiter">&quot;</span></span>,
                                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameHenry has stats 105</span><span
                                        class="delimiter">&quot;</span></span>,
                                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameNicole has stats 106</span><span
                                        class="delimiter">&quot;</span></span>,
                                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameABSLAJNFOAJNFOANFANSF has stats 121</span><span
                                        class="delimiter">&quot;</span></span>);</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>以一个 Future 开始，其中封装了后续将获取和处理的 ID 的 list。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>获取到 list 后边进一步对其启动异步处理任务。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>对于 list 中的每一个元素：</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>异步地得到相应的 name。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="5"></i><b>5</b></td>
                            <td>异步地得到相应的 statistics。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="6"></i><b>6</b></td>
                            <td>将两个结果一一组合。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="7"></i><b>7</b></td>
                            <td>我们现在有了一个 list，元素是 Future（表示组合的任务，类型是 <code>CompletableFuture</code>），为了执行这些任务，
                                我们需要将这个 list（元素构成的流） 转换为数组（<code>List</code>）。
                            </td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="8"></i><b>8</b></td>
                            <td>将这个数组传递给 <code>CompletableFuture.allOf</code>，返回一个 <code>Future</code> ，当所以任务都完成了，那么这个
                                <code>Future</code>
                                也就完成了。
                            </td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="9"></i><b>9</b></td>
                            <td>有点麻烦的地方在于 <code>allOf</code> 返回的是 <code>CompletableFuture&lt;Void&gt;</code>，所以我们遍历这个
                                Future 的`List`，
                                ，然后使用 <code>join()</code> 来收集它们的结果（不会导致阻塞，因为 <code>AllOf</code> 确保这些 Future 全部完成）
                            </td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="10"></i><b>10</b></td>
                            <td>一旦整个异步流水线被触发，我们等它完成处理，然后返回结果列表。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>由于 Reactor 内置许多组合操作，因此以上例子可以简单地实现：</p>
                </div>
                <div class="listingblock">
                    <div class="title">Reactor 实现与 Future 同样功能的代码</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; ids = ifhrIds(); <i
        class="conum" data-value="1"></i><b>(1)</b>

Flux&lt;<span class="predefined-type">String</span>&gt; combinations =
                ids.flatMap(id -&gt; { <i class="conum" data-value="2"></i><b>(2)</b>
                        Mono&lt;<span class="predefined-type">String</span>&gt; nameTask = ifhrName(id); <i
            class="conum" data-value="3"></i><b>(3)</b>
                        Mono&lt;<span class="predefined-type">Integer</span>&gt; statTask = ifhrStat(id); <i
            class="conum" data-value="4"></i><b>(4)</b>

                        <span class="keyword">return</span> nameTask.zipWith(statTask, <i class="conum"
                                                                                          data-value="5"></i><b>(5)</b>
                                        (name, stat) -&gt; <span class="string"><span
            class="delimiter">&quot;</span><span class="content">Name </span><span
            class="delimiter">&quot;</span></span> + name + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> has stats </span><span class="delimiter">&quot;</span></span> + stat);
                });

Mono&lt;<span class="predefined-type">List</span>&lt;<span class="predefined-type">String</span>&gt;&gt; result = combinations.collectList(); <i
            class="conum" data-value="6"></i><b>(6)</b>

<span class="predefined-type">List</span>&lt;<span
            class="predefined-type">String</span>&gt; results = result.block(); <i class="conum" data-value="7"></i><b>(7)</b>
assertThat(results).containsExactly( <i class="conum" data-value="8"></i><b>(8)</b>
                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameJoe has stats 103</span><span
                        class="delimiter">&quot;</span></span>,
                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameBart has stats 104</span><span
                        class="delimiter">&quot;</span></span>,
                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameHenry has stats 105</span><span
                        class="delimiter">&quot;</span></span>,
                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameNicole has stats 106</span><span
                        class="delimiter">&quot;</span></span>,
                <span class="string"><span class="delimiter">&quot;</span><span class="content">Name NameABSLAJNFOAJNFOANFANSF has stats 121</span><span
                        class="delimiter">&quot;</span></span>
);</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>这一次，我们从一个异步方式提供的 <code>ids</code> 序列（<code>Flux&lt;String&gt;</code>）开始。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>对于序列中的每一个元素，我们异步地处理它（<code>flatMap</code> 方法内）两次。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>获取相应的 name。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>获取相应的 statistic.</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="5"></i><b>5</b></td>
                            <td>异步地组合两个值。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="6"></i><b>6</b></td>
                            <td>随着序列中的元素值“到位”，它们收集一个 <code>List</code> 中。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="7"></i><b>7</b></td>
                            <td>在生成流的环节，我们可以继续异步地操作 <code>Flux</code> 流，对其进行组合和订阅（subscribe）。
                                最终我们很可能得到一个 <code>Mono</code> 。由于是测试，我们阻塞住（<code>block()</code>），等待流处理过程结束，
                                然后直接返回集合。
                            </td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="8"></i><b>8</b></td>
                            <td>Assert 结果。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>回调或 Future 遇到的窘境是类似的，这也是响应式编程要通过 <code>Publisher-Suscriber</code> 方式来解决的。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_从命令式编程到响应式编程"><a class="anchor" href="#_从命令式编程到响应式编程"></a>3.3. 从命令式编程到响应式编程</h3>
                <div class="paragraph">
                    <p>类似 Reactor 这样的响应式库的目标就是要弥补上述“经典”的 JVM 异步方式所带来的不足，
                        此外还会关注一下几个方面：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><strong>可编排性（Composability）</strong> 以及 <strong>可读性（Readability）</strong></p>
                        </li>
                        <li>
                            <p>使用丰富的 <strong>操作符</strong> 来处理形如 <strong>流</strong> 的数据</p>
                        </li>
                        <li>
                            <p>在 <strong>订阅（subscribe）</strong> 之前什么都不会发生</p>
                        </li>
                        <li>
                            <p><strong>背压（backpressure）</strong> 具体来说即 <em>消费者能够反向告知生产者生产内容的速度的能力</em></p>
                        </li>
                        <li>
                            <p><strong>高层次</strong> （同时也是有高价值的）的抽象，从而达到 <em>并发无关</em> 的效果</p>
                        </li>
                    </ul>
                </div>
                <div class="sect3">
                    <h4 id="_可编排性与可读性"><a class="anchor" href="#_可编排性与可读性"></a>3.3.1. 可编排性与可读性</h4>
                    <div class="paragraph">
                        <p>可编排性，指的是编排多个异步任务的能力。比如我们将前一个任务的结果传递给后一个任务作为输入，
                            或者将多个任务以分解再汇总（fork-join）的形式执行，或者将异步的任务作为离散的组件在系统中
                            进行重用。</p>
                    </div>
                    <div class="paragraph">
                        <p>这种编排任务的能力与代码的可读性和可维护性是紧密相关的。随着异步处理任务数量和复杂度
                            的提高，编写和阅读代码都变得越来越困难。就像我们刚才看到的，回调模式是简单的，但是缺点
                            是在复杂的处理逻辑中，回调中会层层嵌入回调，导致 <strong>回调地狱（Callback Hell）</strong> 。你能猜到
                            （或有过这种痛苦经历），这样的代码是难以阅读和分析的。</p>
                    </div>
                    <div class="paragraph">
                        <p>Reactor 提供了丰富的编排操作，从而代码直观反映了处理流程，并且所有的操作保持在同一层次
                            （尽量避免了嵌套）。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_就像装配流水线"><a class="anchor" href="#_就像装配流水线"></a>3.3.2. 就像装配流水线</h4>
                    <div class="paragraph">
                        <p>你可以想象数据在响应式应用中的处理，就像流过一条装配流水线。Reactor 既是传送带，
                            又是一个个的装配工或机器人。原材料从源头（最初的 <code>Publisher</code>）流出，最终被加工为成品，
                            等待被推送到消费者（或者说 <code>Subscriber</code>）。</p>
                    </div>
                    <div class="paragraph">
                        <p>原材料会经过不同的中间处理过程，或者作为半成品与其他半成品进行组装。如果某处有齿轮卡住，
                            或者某件产品的包装过程花费了太久时间，相应的工位就可以向上游发出信号来限制或停止发出原材料。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_操作符_operators"><a class="anchor" href="#_操作符_operators"></a>3.3.3. 操作符（Operators）</h4>
                    <div class="paragraph">
                        <p>在 Reactor 中，操作符（operator）就像装配线中的工位（操作员或装配机器人）。每一个操作符
                            对 <code>Publisher</code> 进行相应的处理，然后将 <code>Publisher</code> 包装为一个新的 <code>Publisher</code>。就像一个链条，
                            数据源自第一个 <code>Publisher</code>，然后顺链条而下，在每个环节进行相应的处理。最终，一个订阅者
                            (<code>Subscriber</code>）终结这个过程。请记住，在订阅者（<code>Subscriber</code>）订阅（subscribe）到一个
                            发布者（<code>Publisher</code>）之前，什么都不会发生。</p>
                    </div>
                    <div class="admonitionblock tip">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-tip" title="Tip"></i>
                                </td>
                                <td class="content">
                                    理解了操作符会创建新的 <code>Publisher</code> 实例这一点，能够帮助你避免一个常见的问题，
                                    这种问题会让你觉得处理链上的某个操作符没有起作用。相关内容请参考 <a href="#faq.chain">item</a> 。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>虽然响应式流规范（Reactive Streams specification）没有规定任何操作符， 类似 Reactor
                            这样的响应式库所带来的最大附加价值之一就是提供丰富的操作符。包括基础的转换操作，
                            到过滤操作，甚至复杂的编排和错误处理操作。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="reactive.subscribe"><a class="anchor" href="#reactive.subscribe"></a>3.3.4. <code>subscribe()</code>
                        之前什么都不会发生</h4>
                    <div class="paragraph">
                        <p>在 Reactor 中，当你创建了一条 <code>Publisher</code> 处理链，数据还不会开始生成。事实上，你是创建了
                            一种抽象的对于异步处理流程的描述（从而方便重用和组装）。</p>
                    </div>
                    <div class="paragraph">
                        <p>当真正“订阅（subscrib）”的时候，你需要将 <code>Publisher</code> 关联到一个 <code>Subscriber</code> 上，然后
                            才会触发整个链的流动。这时候，<code>Subscriber</code> 会向上游发送一个 <code>request</code> 信号，一直到达源头
                            的 <code>Publisher</code>。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="reactive.backpressure"><a class="anchor" href="#reactive.backpressure"></a>3.3.5. 背压（）</h4>
                    <div class="paragraph">
                        <p>向上游传递信号这一点也被用于实现 <strong>背压</strong> ，就像在装配线上，某个工位的处理速度如果慢于流水线
                            速度，会对上游发送反馈信号一样。</p>
                    </div>
                    <div class="paragraph">
                        <p>在响应式流规范中实际定义的机制同刚才的类比非常接近：订阅者可以无限接受数据并让它的源头
                            “满负荷”推送所有的数据，也可以通过使用 <code>request</code> 机制来告知源头它一次最多能够处理 <code>n</code>
                            个元素。</p>
                    </div>
                    <div class="paragraph">
                        <p>中间环节的操作也可以影响 <code>request</code>。想象一个能够将每10个元素分批打包的缓存（<code>buffer</code>）操作。
                            如果订阅者请求一个元素，那么对于源头来说可以生成10个元素。此外预取策略也可以使用了，
                            比如在订阅前预先生成元素。</p>
                    </div>
                    <div class="paragraph">
                        <p>这样能够将“推送”模式转换为“推送+拉取”混合的模式，如果下游准备好了，可以从上游拉取 n
                            个元素；但是如果上游元素还没有准备好，下游还是要等待上游的推送。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="reactive.hotCold"><a class="anchor" href="#reactive.hotCold"></a>3.3.6. 热（Hot） vs 冷（Cold）
                    </h4>
                    <div class="paragraph">
                        <p>在 Rx 家族的响应式库中，响应式流分为“热”和“冷”两种类型，区别主要在于响应式流如何
                            对订阅者进行响应：</p>
                    </div>
                    <div class="ulist">
                        <ul>
                            <li>
                                <p>一个“冷”的序列，指对于每一个 <code>Subscriber</code>，都会收到从头开始所有的数据。如果源头
                                    生成了一个 HTTP 请求，对于每一个订阅都会创建一个新的 HTTP 请求。</p>
                            </li>
                            <li>
                                <p>一个“热”的序列，指对于一个 <code>Subscriber</code>，只能获取从它开始
                                    订阅 <em>之后</em> 发出的数据。不过注意，有些“热”的响应式流可以缓存部分或全部历史数据。
                                    通常意义上来说，一个“热”的响应式流，甚至在即使没有订阅者接收数据的情况下，也可以
                                    发出数据（这一点同 “<code>Subscribe()</code> 之前什么都不会发生”的规则有冲突）。</p>
                            </li>
                        </ul>
                    </div>
                    <div class="paragraph">
                        <p>更多关于 Reactor 中“热”vs“冷”的内容，请参考 <a href="#reactor.hotCold">this reactor-specific section</a>。
                        </p>
                    </div>
                    <div class="paragraph">
                        <p><a class="fa fa-edit"
                              href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/reactiveProgramming.adoc"
                              title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                            - "<a href="#intro-reactive">响应式编程</a>"</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="core-features"><a class="anchor" href="#core-features"></a>4. Reactor 核心特性</h2>
        <div class="sectionbody">
            <div class="paragraph">
                <p>Reactor 项目的主要 artifact 是 <code>reactor-core</code>，这是一个基于 Java 8 的实现了响应式流规范
                    （Reactive Streams specification）的响应式库。</p>
            </div>
            <div class="paragraph">
                <p>Reactor 引入了实现 <code>Publisher</code> 的响应式类 <code>Flux</code> 和 <code>Mono</code>，以及丰富的操作方式。
                    一个 <code>Flux</code> 对象代表一个包含 0..N 个元素的响应式序列，而一个 <code>Mono</code> 对象代表一个包含
                    零/一个（0..1）元素的结果。</p>
            </div>
            <div class="paragraph">
                <p>这种区别为这俩类型带来了语义上的信息——表明了异步处理逻辑所面对的元素基数。比如，
                    一个 HTTP 请求产生一个响应，所以对其进行 <code>count</code> 操作是没有多大意义的。表示这样一个
                    结果的话，应该用 <code>Mono&lt;HttpResponse&gt;</code> 而不是 <code>Flux&lt;HttpResponse&gt;</code>，因为要置于其上的
                    操作通常只用于处理 0/1 个元素。</p>
            </div>
            <div class="paragraph">
                <p>有些操作可以改变基数，从而需要切换类型。比如，<code>count</code> 操作用于 <code>Flux</code>，但是操作
                    返回的结果是 <code>Mono&lt;Long&gt;</code>。</p>
            </div>
            <div class="sect2">
                <h3 id="flux"><a class="anchor" href="#flux"></a>4.1. <code>Flux</code>, 包含 0-N 个元素的异步序列</h3>
                <div class="imageblock">
                    <div class="content">
                        <img alt="Flux"
                             src="https://raw.githubusercontent.com/reactor/reactor-core/v3.0.7.RELEASE/src/docs/marble/flux.png">
                    </div>
                </div>
                <div class="paragraph">
                    <p><code>Flux&lt;T&gt;</code> 是一个能够发出 0 到 N 个元素的标准的 <code>Publisher&lt;T&gt;</code>，它会被一个“错误（error）”
                        或“完成（completion）”信号终止。因此，一个 flux 的可能结果是一个 value、completion 或 error。
                        就像在响应式流规范中规定的那样，这三种类型的信号被翻译为面向下游的 <code>onNext</code>，`onComplete`和`onError`方法。</p>
                </div>
                <div class="paragraph">
                    <p>由于多种不同的信号可能性，<code>Flux</code> 可以作为一种通用的响应式类型。注意，所有的信号事件，
                        包括代表终止的信号事件都是可选的：如果没有 <code>onNext</code> 事件但是有一个 <code>onComplete</code> 事件，
                        那么发出的就是 <em>空的</em> 有限序列，但是去掉 <code>onComplete</code> 那么得到的就是一个 <em>无限的</em> 空序列。
                        当然，无限序列也可以不是空序列，比如，<code>Flux.interval(Duration)</code> 生成的是一个 <code>Flux&lt;Long&gt;</code>，
                        这就是一个无限地周期性发出规律 tick 的时钟序列。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="mono"><a class="anchor" href="#mono"></a>4.2. <code>Mono</code>, 异步的 0-1 结果</h3>
                <div class="imageblock">
                    <div class="content">
                        <img alt="Mono"
                             src="https://raw.githubusercontent.com/reactor/reactor-core/v3.0.7.RELEASE/src/docs/marble/mono.png">
                    </div>
                </div>
                <div class="paragraph">
                    <p><code>Mono&lt;T&gt;</code> 是一种特殊的 <code>Publisher&lt;T&gt;</code>， 它最多发出一个元素，然后终止于一个 <code>onComplete</code>
                        信号或一个 <code>onError</code> 信号。</p>
                </div>
                <div class="paragraph">
                    <p>它只适用其中一部分可用于 <code>Flux</code> 的操作。比如，（两个 <code>Mono</code> 的）结合类操作可以忽略其中之一
                        而发出另一个 <code>Mono</code>，也可以将两个都发出，对于后一种情况会切换为一个 <code>Flux</code>。</p>
                </div>
                <div class="paragraph">
                    <p>例如，<code>Mono#concatWith(Publisher)</code> 返回一个 <code>Flux</code>，而 <code>Mono#then(Mono)</code>
                        返回另一个 <code>Mono</code>。</p>
                </div>
                <div class="paragraph">
                    <p>注意，<code>Mono</code> 可以用于表示“空”的只有完成概念的异步处理（比如 <code>Runnable</code>）。这种用
                        <code>Mono&lt;Void&gt;</code> 来创建。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_简单的创建和订阅_flux_或_mono_的方法"><a class="anchor" href="#_简单的创建和订阅_flux_或_mono_的方法"></a>4.3. 简单的创建和订阅
                    Flux 或 Mono 的方法</h3>
                <div class="paragraph">
                    <p>最简单的上手 <code>Flux</code> 和 <code>Mono</code> 的方式就是使用相应类提供的多种工厂方法之一。</p>
                </div>
                <div class="paragraph">
                    <p>比如，如果要创建一个 <code>String</code> 的序列，你可以直接列举它们，或者将它们放到一个集合里然后用来创建
                        Flux，如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; seq1 = Flux.just(<span
        class="string"><span class="delimiter">&quot;</span><span class="content">foo</span><span class="delimiter">&quot;</span></span>, <span
        class="string"><span class="delimiter">&quot;</span><span class="content">bar</span><span class="delimiter">&quot;</span></span>, <span
        class="string"><span class="delimiter">&quot;</span><span class="content">foobar</span><span class="delimiter">&quot;</span></span>);

<span class="predefined-type">List</span>&lt;<span class="predefined-type">String</span>&gt; iterable = <span
            class="predefined-type">Arrays</span>.asList(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">foo</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">bar</span><span class="delimiter">&quot;</span></span>, <span
            class="string"><span class="delimiter">&quot;</span><span class="content">foobar</span><span
            class="delimiter">&quot;</span></span>);
Flux&lt;<span class="predefined-type">String</span>&gt; seq2 = Flux.fromIterable(iterable);</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>工厂方法的其他例子如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Mono&lt;<span class="predefined-type">String</span>&gt; noData = Mono.empty(); <i
        class="conum" data-value="1"></i><b>(1)</b>

Mono&lt;<span class="predefined-type">String</span>&gt; data = Mono.just(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">foo</span><span class="delimiter">&quot;</span></span>);

Flux&lt;<span class="predefined-type">Integer</span>&gt; numbersFromFiveToSeven = Flux.range(<span
            class="integer">5</span>, <span class="integer">3</span>); <i class="conum"
                                                                          data-value="2"></i><b>(2)</b></code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>注意，即使没有值，工厂方法仍然采用通用的返回类型。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>第一个参数是 range 的开始，第二个参数是要生成的元素个数。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>在订阅（subscribe）的时候，<code>Flux</code> 和 <code>Mono</code> 使用 Java 8 lambda 表达式。
                        <code>.subscribe()</code> 方法有多种不同的方法签名，你可以传入各种不同的 lambda
                        形式的参数来定义回调。如下所示：</p>
                </div>
                <div class="listingblock" id="subscribeMethods">
                    <div class="title">基于 lambda 的对 <code>Flux</code> 的订阅（subscribe）</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">subscribe(); <i class="conum" data-value="1"></i><b>(1)</b>

subscribe(Consumer&lt;? <span class="local-variable">super</span> T&gt; consumer); <i class="conum"
                                                                                      data-value="2"></i><b>(2)</b>

subscribe(Consumer&lt;? <span class="local-variable">super</span> T&gt; consumer,
          Consumer&lt;? <span class="local-variable">super</span> <span class="predefined-type">Throwable</span>&gt; errorConsumer); <i
            class="conum" data-value="3"></i><b>(3)</b>

subscribe(Consumer&lt;? <span class="local-variable">super</span> T&gt; consumer,
          Consumer&lt;? <span class="local-variable">super</span> <span class="predefined-type">Throwable</span>&gt; errorConsumer,
          <span class="predefined-type">Runnable</span> completeConsumer); <i class="conum"
                                                                              data-value="4"></i><b>(4)</b>

subscribe(Consumer&lt;? <span class="local-variable">super</span> T&gt; consumer,
          Consumer&lt;? <span class="local-variable">super</span> <span class="predefined-type">Throwable</span>&gt; errorConsumer,
          <span class="predefined-type">Runnable</span> completeConsumer,
          Consumer&lt;? <span class="local-variable">super</span> Subscription&gt; subscriptionConsumer); <i
            class="conum" data-value="5"></i><b>(5)</b></code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>订阅并触发序列。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>对每一个生成的元素进行消费。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>对正常元素进行消费，也对错误进行响应。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>对正常元素和错误均有响应，还定义了序列正常完成后的回调。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="5"></i><b>5</b></td>
                            <td>对正常元素、错误和完成信号均有响应， 同时也定义了对该 <code>subscribe</code> 方法返回的
                                <code>Subscription</code> 执行的回调。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="admonitionblock tip">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-tip" title="Tip"></i>
                            </td>
                            <td class="content">
                                以上方法会返回一个 <code>Subscription</code> 的引用，如果不再需要更多元素你可以通过它来取消订阅。
                                取消订阅时， 源头会停止生成新的数据，并清理相关资源。取消和清理的操作在 Reactor 中是在
                                接口 <code>Disposable</code> 中定义的。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="sect3">
                    <h4 id="_code_subscribe_code_方法示例"><a class="anchor" href="#_code_subscribe_code_方法示例"></a>4.3.1.
                        <code>subscribe</code> 方法示例</h4>
                    <div class="paragraph">
                        <p>这一小节包含了对 <code>subscribe</code> 的5个不同签名的方法的示例，如下是一个无参的基本方法的使用：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">Integer</span>&gt; ints = Flux.range(<span
        class="integer">1</span>, <span class="integer">3</span>); <i class="conum" data-value="1"></i><b>(1)</b>
ints.subscribe(); <i class="conum" data-value="2"></i><b>(2)</b></code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>配置一个在订阅时会产生3个值的 <code>Flux</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>最简单的订阅方式。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>第二行代码没有任何输出，但是它确实执行了。<code>Flux</code> 产生了3个值。如果我们传入一个 lambda，
                            我们就可以看到这几个值，如下一个列子：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">Integer</span>&gt; ints = Flux.range(<span
        class="integer">1</span>, <span class="integer">3</span>); <i class="conum" data-value="1"></i><b>(1)</b>
ints.subscribe(i -&gt; <span class="predefined-type">System</span>.out.println(i)); <i class="conum" data-value="2"></i><b>(2)</b></code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>配置一个在订阅时会产生3个值的 <code>Flux</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>订阅它并打印值。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>第二行代码会输入如下内容：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>1
2
3</pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>为了演示下一个方法签名，我们故意引入一个错误，如下所示：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">Integer</span>&gt; ints = Flux.range(<span
        class="integer">1</span>, <span class="integer">4</span>) <i class="conum" data-value="1"></i><b>(1)</b>
      .map(i -&gt; { <i class="conum" data-value="2"></i><b>(2)</b>
        <span class="keyword">if</span> (i &lt;= <span class="integer">3</span>) <span class="keyword">return</span> i; <i
            class="conum" data-value="3"></i><b>(3)</b>
        <span class="keyword">throw</span> <span class="keyword">new</span> <span
            class="exception">RuntimeException</span>(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Got to 4</span><span class="delimiter">&quot;</span></span>); <i class="conum"
                                                                                             data-value="4"></i><b>(4)</b>
      });
ints.subscribe(i -&gt; <span class="predefined-type">System</span>.out.println(i), <i class="conum"
                                                                                      data-value="5"></i><b>(5)</b>
      error -&gt; <span class="predefined-type">System</span>.err.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Error: </span><span class="delimiter">&quot;</span></span> + error));</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>配置一个在订阅时会产生4个值的 <code>Flux</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>为了对元素进行处理，我们需要一个 map 操作。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>对于多数元素，返回值本身。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="4"></i><b>4</b></td>
                                <td>对其中一个元素抛出错误。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="5"></i><b>5</b></td>
                                <td>订阅的时候定义如何进行错误处理。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>现在我们有两个 lambda 表达式：一个是用来处理正常数据，一个用来处理错误。
                            刚才的代码输出如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>1
2
3
Error: java.lang.RuntimeException: Got to 4</pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>下一个 <code>subscribe</code> 方法的签名既有错误处理，还有一个完成后的处理，如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">Integer</span>&gt; ints = Flux.range(<span
        class="integer">1</span>, <span class="integer">4</span>); <i class="conum" data-value="1"></i><b>(1)</b>
ints.subscribe(i -&gt; <span class="predefined-type">System</span>.out.println(i),
    error -&gt; <span class="predefined-type">System</span>.err.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Error </span><span class="delimiter">&quot;</span></span> + error),
    () -&gt; {<span class="predefined-type">System</span>.out.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Done</span><span class="delimiter">&quot;</span></span>);}); <i class="conum"
                                                                                            data-value="2"></i><b>(2)</b></code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>配置一个在订阅时会产生4个值的 <code>Flux</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>订阅时定义错误和完成信号的处理。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>错误和完成信号都是终止信号，并且二者只会出现其中之一。为了能够最终全部正常完成，你必须处理错误信号。</p>
                    </div>
                    <div class="paragraph">
                        <p>用于处理完成信号的 lambda 是一对空的括号，因为它实际上匹配的是 <code>Runnalbe</code> 接口中的 <code>run</code> 方法，
                            不接受参数。刚才的代码输出如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>1
2
3
4
Done</pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>最后一个 <code>subscribe</code> 方法签名包含一个自定义的 <code>subscriber</code>（下一节会介绍到）：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">SampleSubscriber&lt;<span class="predefined-type">Integer</span>&gt; ss = <span
        class="keyword">new</span> SampleSubscriber&lt;<span class="predefined-type">Integer</span>&gt;();
Flux&lt;<span class="predefined-type">Integer</span>&gt; ints = Flux.range(<span class="integer">1</span>, <span
            class="integer">4</span>);
ints.subscribe(i -&gt; <span class="predefined-type">System</span>.out.println(i),
    error -&gt; <span class="predefined-type">System</span>.err.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Error </span><span class="delimiter">&quot;</span></span> + error),
    () -&gt; {<span class="predefined-type">System</span>.out.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Done</span><span class="delimiter">&quot;</span></span>);},
    s -&gt; ss.request(<span class="integer">10</span>));
ints.subscribe(ss);</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>上面这个例子中，我们把一个自定义的 <code>Subscriber</code> 作为 <code>subscribe</code> 方法的最后一个参数。
                            下边的例子是这个自定义的 <code>Subscriber</code>，这是一个对 <code>Subscriber</code> 的最简单实现：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">package</span> <span class="namespace">io.projectreactor.samples</span>;

<span class="keyword">import</span> <span class="include">org.reactivestreams.Subscription</span>;

<span class="keyword">import</span> <span class="include">reactor.core.publisher.BaseSubscriber</span>;

<span class="directive">public</span> <span class="type">class</span> <span class="class">SampleSubscriber</span>&lt;T&gt; <span
            class="directive">extends</span> BaseSubscriber&lt;T&gt; {

        <span class="directive">public</span> <span class="type">void</span> hookOnSubscribe(Subscription subscription) {
                <span class="predefined-type">System</span>.out.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Subscribed</span><span class="delimiter">&quot;</span></span>);
                request(<span class="integer">1</span>);
        }

        <span class="directive">public</span> <span class="type">void</span> hookOnNext(T value) {
                <span class="predefined-type">System</span>.out.println(value);
                request(<span class="integer">1</span>);
        }
}</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p><code>SampleSubscriber</code> 类继承自 <code>BaseSubscriber</code>，在 Reactor 中, 推荐用户扩展它来实现自定义的
                            <code>Subscriber</code>。这个类提供了一些 hook 方法，我们可以通过重写它们来调整 subscriber 的行为。
                            默认情况下，它会触发一个无限个数的请求，但是当你想自定义请求元素的个数的时候，扩展
                            <code>BaseSubscriber</code> 就很方便了。</p>
                    </div>
                    <div class="paragraph">
                        <p>扩展的时候通常至少要覆盖 <code>hookOnSubscribe(Subscription subscription)</code> 和 <code>hookOnNext(T
                            value)</code>
                            这两个方法。这个例子中， <code>hookOnSubscribe</code> 方法打印一段话到标准输出，然后进行第一次请求。
                            然后 <code>hookOnNext</code> 同样进行了打印，同时逐个处理剩余请求。</p>
                    </div>
                    <div class="paragraph">
                        <p><code>SampleSubscriber</code> 输出如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>Subscribed
1
2
3
4</pre>
                        </div>
                    </div>
                    <div class="admonitionblock note">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-note" title="Note"></i>
                                </td>
                                <td class="content">
                                    建议你同时重写 <code>hookOnError</code>、<code>hookOnCancel</code>，以及
                                    <code>hookOnComplete</code> 方法。
                                    你最好也重写 <code>hookFinally</code> 方法。<code>SampleSubscribe</code> 确实是一个最简单的实现了
                                    请求有限个数元素的 <code>Subscriber</code>。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>本文档后边还会再讨论 <code>BaseSubscriber</code>。</p>
                    </div>
                    <div class="paragraph">
                        <p>响应式流规范定义了另一个 <code>subscribe</code> 方法的签名，它只接收一个自定义的 <code>Subscriber</code>，
                            没有其他的参数，如下所示：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
                            <pre class="CodeRay highlight"><code data-lang="java">subscribe(Subscriber&lt;? <span
                                    class="local-variable">super</span> T&gt; subscriber);</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>如果你已经有一个 <code>Subscriber</code>，那么这个方法签名还是挺有用的。况且，你可能还会用到它
                            来做一些订阅相关（subscription-related）的回调。比如，你想要自定义“背压（backpressure）”
                            并且自己来触发请求。</p>
                    </div>
                    <div class="paragraph">
                        <p>在这种情况下，使用 <code>BaseSubscriber</code> 抽象类就很方便，因为它提供了很好的配置“背压”
                            的方法。</p>
                    </div>
                    <div class="listingblock">
                        <div class="title">使用 <code>BaseSubscriber</code> 来配置“背压”</div>
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; source = someStringSource();

source.map(<span class="predefined-type">String</span>::toUpperCase)
      .subscribe(<span class="keyword">new</span> BaseSubscriber&lt;<span class="predefined-type">String</span>&gt;() { <i
            class="conum" data-value="1"></i><b>(1)</b>
          <span class="annotation">@Override</span>
          <span class="directive">protected</span> <span class="type">void</span> hookOnSubscribe(Subscription subscription) {
              <i class="conum" data-value="2"></i><b>(2)</b>
              request(<span class="integer">1</span>); <i class="conum" data-value="3"></i><b>(3)</b>
          }

          <span class="annotation">@Override</span>
          <span class="directive">protected</span> <span class="type">void</span> hookOnNext(<span
            class="predefined-type">String</span> value) {
              request(<span class="integer">1</span>); <i class="conum" data-value="4"></i><b>(4)</b>
          }

          <i class="conum" data-value="5"></i><b>(5)</b>
      });</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td><code>BaseSubscriber</code> 是一个抽象类，所以我们创建一个匿名内部类。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td><code>BaseSubscriber</code> 定义了多种用于处理不同信号的 hook。它还定义了一些捕获 <code>Subscription</code>
                                    对象的现成方法，这些方法可以用在 hook 中。
                                </td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td><code>request(n)</code> 就是这样一个方法。它能够在任何 hook 中，通过 subscription 向上游传递
                                    背压请求。这里我们在开始这个流的时候请求1个元素值。
                                </td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="4"></i><b>4</b></td>
                                <td>随着接收到新的值，我们继续以每次请求一个元素的节奏从源头请求值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="5"></i><b>5</b></td>
                                <td>其他 hooks 有 <code>hookOnComplete</code>, <code>hookOnError</code>,
                                    <code>hookOnCancel</code>, and <code>hookFinally</code>
                                    （它会在流终止的时候被调用，传入一个 <code>SignalType</code> 作为参数）。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="admonitionblock warning">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-warning" title="Warning"></i>
                                </td>
                                <td class="content">
                                    当你修改请求操作的时候，你必须注意让 subscriber 向上提出足够的需求，
                                    否则上游的 Flux 可能会被“卡住”。所以 <code>BaseSubscriber</code> 在进行扩展的时候要覆盖
                                    <code>hookOnSubscribe</code>
                                    和 <code>onNext</code>，这样你至少会调用 <code>request</code> 一次。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p><code>BaseSubscriber</code> 还提供了 <code>requestUnbounded()</code> 方法来切换到“无限”模式（等同于 <code>request(Long.MAX_VALUE)</code>）。
                        </p>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="producing"><a class="anchor" href="#producing"></a>4.4. 可编程式地创建一个序列</h3>
                <div class="paragraph">
                    <p>在这一小节，我们介绍如何通过定义相对应的事件（<code>onNext</code>、<code>onError`和`onComplete</code>）
                        创建一个 <code>Flux</code> 或 <code>Mono</code>。所有这些方法都通过 API 来触发我们叫做 <strong>sink（池）</strong> 的事件。
                        sink 的类型不多，我们快速过一下。</p>
                </div>
                <div class="sect3">
                    <h4 id="producing.generate"><a class="anchor" href="#producing.generate"></a>4.4.1. Generate</h4>
                    <div class="paragraph">
                        <p>最简单的创建 <code>Flux</code> 的方式就是使用 <code>generate</code> 方法。</p>
                    </div>
                    <div class="paragraph">
                        <p>这是一种 <strong>同步地</strong>， <strong>逐个地</strong> 产生值的方法，意味着 sink 是一个
                            <code>SynchronousSink</code>
                            而且其 <code>next()</code> 方法在每次回调的时候最多只能被调用一次。你也可以调用 <code>error(Throwable)</code>
                            或者 <code>complete()</code>，不过是可选的。</p>
                    </div>
                    <div class="paragraph">
                        <p>最有用的一种方式就是同时能够记录一个状态值（state），从而在使用 sink 发出下一个元素的时候能够
                            基于这个状态值去产生元素。此时生成器（generator）方法就是一个 <code>BiFunction&lt;S, SynchronousSink&lt;T&gt;,
                                S&gt;</code>，
                            其中 <code>&lt;S&gt;</code> 是状态对象的类型。你需要提供一个 <code>Supplier&lt;S&gt;</code> 来初始化状态值，而生成器需要
                            在每一“回合”生成元素后返回新的状态值（供下一回合使用）。</p>
                    </div>
                    <div class="paragraph">
                        <p>例如我们使用一个 <code>int</code> 作为状态值。</p>
                    </div>
                    <div class="listingblock">
                        <div class="title">基于状态值的 <code>generate</code> 示例</div>
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux = Flux.generate(
    () -&gt; <span class="integer">0</span>, <i class="conum" data-value="1"></i><b>(1)</b>
    (state, sink) -&gt; {
      sink.next(<span class="string"><span class="delimiter">&quot;</span><span class="content">3 x </span><span
            class="delimiter">&quot;</span></span> + state + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> = </span><span class="delimiter">&quot;</span></span> + <span
            class="integer">3</span>*state); <i class="conum" data-value="2"></i><b>(2)</b>
      <span class="keyword">if</span> (state == <span class="integer">10</span>) sink.complete(); <i class="conum"
                                                                                                     data-value="3"></i><b>(3)</b>
      <span class="keyword">return</span> state + <span class="integer">1</span>; <i class="conum"
                                                                                     data-value="4"></i><b>(4)</b>
    });</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>初始化状态值（state）为0。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>我们基于状态值 state 来生成下一个值（state 乘以 3）。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>我们也可以用状态值来决定什么时候终止序列。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="4"></i><b>4</b></td>
                                <td>返回一个新的状态值 state，用于下一次调用。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>上面的代码生成了“3 x”的乘法表：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>3 x 0 = 0
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
3 x 4 = 12
3 x 5 = 15
3 x 6 = 18
3 x 7 = 21
3 x 8 = 24
3 x 9 = 27
3 x 10 = 30</pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>我们也可以使用可变（mutable）类型（译者注：如上例，原生类型及其包装类，以及String等属于不可变类型）
                            的 <code>&lt;S&gt;</code>。上边的例子也可以用 <code>AtomicLong</code> 作为状态值，在每次生成后改变它的值。</p>
                    </div>
                    <div class="listingblock">
                        <div class="title">可变类型的状态变量</div>
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux = Flux.generate(
    <span class="predefined-type">AtomicLong</span>::<span class="keyword">new</span>, <i class="conum"
                                                                                          data-value="1"></i><b>(1)</b>
    (state, sink) -&gt; {
      <span class="type">long</span> i = state.getAndIncrement(); <i class="conum" data-value="2"></i><b>(2)</b>
      sink.next(<span class="string"><span class="delimiter">&quot;</span><span class="content">3 x </span><span
            class="delimiter">&quot;</span></span> + i + <span class="string"><span class="delimiter">&quot;</span><span
            class="content"> = </span><span class="delimiter">&quot;</span></span> + <span class="integer">3</span>*i);
      <span class="keyword">if</span> (i == <span class="integer">10</span>) sink.complete();
      <span class="keyword">return</span> state; <i class="conum" data-value="3"></i><b>(3)</b>
    });</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>这次我们初始化一个可变类型的状态值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>改变状态值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>返回 <strong>同一个</strong> 实例作为新的状态值。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="admonitionblock tip">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-tip" title="Tip"></i>
                                </td>
                                <td class="content">
                                    如果状态对象需要清理资源，可以使用 <code>generate(Supplier&lt;S&gt;, BiFunction,
                                    Consumer&lt;S&gt;)</code>
                                    这个签名方法来清理状态对象（译者注：Comsumer 在序列终止才被调用）。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>下面是一个在 generate 方法中增加 <code>Consumer</code> 的例子：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux = Flux.generate(
    <span class="predefined-type">AtomicLong</span>::<span class="keyword">new</span>,
      (state, sink) -&gt; { <i class="conum" data-value="1"></i><b>(1)</b>
      <span class="type">long</span> i = state.getAndIncrement(); <i class="conum" data-value="2"></i><b>(2)</b>
      sink.next(<span class="string"><span class="delimiter">&quot;</span><span class="content">3 x </span><span
            class="delimiter">&quot;</span></span> + i + <span class="string"><span class="delimiter">&quot;</span><span
            class="content"> = </span><span class="delimiter">&quot;</span></span> + <span class="integer">3</span>*i);
      <span class="keyword">if</span> (i == <span class="integer">10</span>) sink.complete();
      <span class="keyword">return</span> state; <i class="conum" data-value="3"></i><b>(3)</b>
    }, (state) -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">state: </span><span
            class="delimiter">&quot;</span></span> + state)); <i class="conum" data-value="4"></i><b>(4)</b>
}</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>同样，初始化一个可变对象作为状态变量。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>改变状态。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>返回 <strong>同一个</strong> 实例作为新的状态。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="4"></i><b>4</b></td>
                                <td>我们会看到最后一个状态值（11）会被这个 <code>Consumer</code> lambda 输出。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>如果 state 使用了数据库连接或者其他需要最终进行清理的资源，这个 <code>Consumer</code>
                            lambda 可以用来在最后关闭连接或完成相关的其他清理任务。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="producing.create"><a class="anchor" href="#producing.create"></a>4.4.2. Create</h4>
                    <div class="paragraph">
                        <p>作为一个更高级的创建 <code>Flux</code> 的方式， <code>create</code> 方法的生成方式既可以是同步，
                            也可以是异步的，并且还可以每次发出多个元素。</p>
                    </div>
                    <div class="paragraph">
                        <p>该方法用到了 <code>FluxSink</code>，后者同样提供 <code>next</code>，<code>error</code> 和
                            <code>complete</code> 等方法。
                            与 <code>generate</code> 不同的是，<code>create</code> 不需要状态值，另一方面，它可以在回调中触发
                            多个事件（即使是在未来的某个时间）。</p>
                    </div>
                    <div class="admonitionblock tip">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-tip" title="Tip"></i>
                                </td>
                                <td class="content">
                                    <code>create</code> 有个好处就是可以将现有的 API 转为响应式，比如监听器的异步方法。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>假设你有一个监听器 API，它按 chunk 处理数据，有两种事件：（1）一个 chunk
                            数据准备好的事件；（2）处理结束的事件。如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="type">interface</span> <span class="class">MyEventListener</span>&lt;T&gt; {
    <span class="type">void</span> onDataChunk(<span class="predefined-type">List</span>&lt;T&gt; chunk);
    <span class="type">void</span> processComplete();
}</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>你可以使用 <code>create</code> 方法将其转化为响应式类型 <code>Flux&lt;T&gt;</code>：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; bridge = Flux.create(sink -&gt; {
    myEventProcessor.register( <i class="conum" data-value="4"></i><b>(4)</b>
      <span class="keyword">new</span> MyEventListener&lt;<span class="predefined-type">String</span>&gt;() { <i
            class="conum" data-value="1"></i><b>(1)</b>

        <span class="directive">public</span> <span class="type">void</span> onDataChunk(<span class="predefined-type">List</span>&lt;<span
            class="predefined-type">String</span>&gt; chunk) {
          <span class="keyword">for</span>(<span class="predefined-type">String</span> s : chunk) {
            sink.next(s); <i class="conum" data-value="2"></i><b>(2)</b>
          }
        }

        <span class="directive">public</span> <span class="type">void</span> processComplete() {
            sink.complete(); <i class="conum" data-value="3"></i><b>(3)</b>
        }
    });
});</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>桥接 <code>MyEventListener</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>每一个 chunk 的数据转化为 <code>Flux</code> 中的一个元素。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td><code>processComplete</code> 事件转换为 <code>onComplete</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="4"></i><b>4</b></td>
                                <td>所有这些都是在 <code>myEventProcessor</code> 执行时异步执行的。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>此外，既然 <code>create</code> 可以是异步地，并且能够控制背压，你可以通过提供一个 <code>OverflowStrategy</code>
                            来定义背压行为。</p>
                    </div>
                    <div class="ulist">
                        <ul>
                            <li>
                                <p><code>IGNORE</code>： 完全忽略下游背压请求，这可能会在下游队列积满的时候导致
                                    <code>IllegalStateException</code>。</p>
                            </li>
                            <li>
                                <p><code>ERROR</code>： 当下游跟不上节奏的时候发出一个 <code>IllegalStateException</code> 的错误信号。</p>
                            </li>
                            <li>
                                <p><code>DROP</code>：当下游没有准备好接收新的元素的时候抛弃这个元素。</p>
                            </li>
                            <li>
                                <p><code>LATEST</code>：让下游只得到上游最新的元素。</p>
                            </li>
                            <li>
                                <p><code>BUFFER</code>：（默认的）缓存所有下游没有来得及处理的元素（这个不限大小的缓存可能导致 <code>OutOfMemoryError</code>）。
                                </p>
                            </li>
                        </ul>
                    </div>
                    <div class="admonitionblock note">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-note" title="Note"></i>
                                </td>
                                <td class="content">
                                    <code>Mono</code> 也有一个用于 <code>create</code> 的生成器（generator）—— <code>MonoSink</code>，它不能生成多个元素，
                                    因此会抛弃第一个元素之后的所有元素。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="sect4">
                        <h5 id="_推送_push_模式"><a class="anchor" href="#_推送_push_模式"></a>推送（push）模式</h5>
                        <div class="paragraph">
                            <p><code>create</code> 的一个变体是 <code>push</code>，适合生成事件流。与 <code>create`类似，`push</code>
                                也可以是异步地，
                                并且能够使用以上各种溢出策略（overflow strategies）管理背压。每次只有一个生成线程可以调用
                                <code>next</code>，<code>complete</code> 或 <code>error</code>。</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; bridge = Flux.push(sink -&gt; {
    myEventProcessor.register(
      <span class="keyword">new</span> SingleThreadEventListener&lt;<span class="predefined-type">String</span>&gt;() { <i
            class="conum" data-value="1"></i><b>(1)</b>

        <span class="directive">public</span> <span class="type">void</span> onDataChunk(<span class="predefined-type">List</span>&lt;<span
            class="predefined-type">String</span>&gt; chunk) {
          <span class="keyword">for</span>(<span class="predefined-type">String</span> s : chunk) {
            sink.next(s); <i class="conum" data-value="2"></i><b>(2)</b>
          }
        }

        <span class="directive">public</span> <span class="type">void</span> processComplete() {
            sink.complete(); <i class="conum" data-value="3"></i><b>(3)</b>
        }

        <span class="directive">public</span> <span class="type">void</span> processError(<span class="predefined-type">Throwable</span> e) {
            sink.error(e); <i class="conum" data-value="4"></i><b>(4)</b>
        }
    });
});</code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>桥接 <code>SingleThreadEventListener</code> API。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td>在监听器所在线程中，事件通过调用 <code>next</code> 被推送到 sink。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="3"></i><b>3</b></td>
                                    <td><code>complete</code> 事件也在同一个线程中。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="4"></i><b>4</b></td>
                                    <td><code>error</code> 事件也在同一个线程中。</td>
                                </tr>
                            </table>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_推送_拉取_push_pull_混合模式"><a class="anchor" href="#_推送_拉取_push_pull_混合模式"></a>推送/拉取（push/pull）混合模式
                        </h5>
                        <div class="paragraph">
                            <p>不像 <code>push</code>，<code>create</code> 可以用于 <code>push</code> 或 <code>pull</code>
                                模式，因此适合桥接监听器的
                                的 API，因为事件消息会随时异步地到来。回调方法 <code>onRequest</code> 可以被注册到 <code>FluxSink</code>
                                以便跟踪请求。这个回调可以被用于从源头请求更多数据，或者通过在下游请求到来
                                的时候传递数据给 sink 以实现背压管理。这是一种推送/拉取混合的模式，
                                因为下游可以从上游拉取已经就绪的数据，上游也可以在数据就绪的时候将其推送到下游。</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; bridge = Flux.create(sink -&gt; {
    myMessageProcessor.register(
      <span class="keyword">new</span> MyMessageListener&lt;<span class="predefined-type">String</span>&gt;() {

        <span class="directive">public</span> <span class="type">void</span> onMessage(<span class="predefined-type">List</span>&lt;<span
            class="predefined-type">String</span>&gt; messages) {
          <span class="keyword">for</span>(<span class="predefined-type">String</span> s : messages) {
            sink.next(s); <i class="conum" data-value="3"></i><b>(3)</b>
          }
        }
    });
    sink.onRequest(n -&gt; {
        <span class="predefined-type">List</span>&lt;<span class="predefined-type">String</span>&gt; messages = myMessageProcessor.request(n); <i
            class="conum" data-value="1"></i><b>(1)</b>
        <span class="keyword">for</span>(<span class="predefined-type">String</span> s : message) {
           sink.next(s); <i class="conum" data-value="2"></i><b>(2)</b>
        }
    });</code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>当有请求的时候取出一个 message。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td>如果有就绪的 message，就发送到 sink。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="3"></i><b>3</b></td>
                                    <td>后续异步到达的 message 也会被发送给 sink。</td>
                                </tr>
                            </table>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_清理_cleaning_up"><a class="anchor" href="#_清理_cleaning_up"></a>清理（Cleaning up）</h5>
                        <div class="paragraph">
                            <p><code>onDispose</code> 和 <code>onCancel</code> 这两个回调用于在被取消和终止后进行清理工作。
                                <code>onDispose</code> 可用于在 <code>Flux</code> 完成，有错误出现或被取消的时候执行清理。
                                <code>onCancel</code> 只用于针对“取消”信号执行相关操作，会先于 <code>onDispose</code> 执行。</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; bridge = Flux.create(sink -&gt; {
    sink.onRequest(n -&gt; channel.poll(n))
        .onCancel(() -&gt; channel.cancel()) <i class="conum" data-value="1"></i><b>(1)</b>
        .onDispose(() -&gt; channel.close())  <i class="conum" data-value="2"></i><b>(2)</b>
    });</code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td><code>onCancel</code> 在取消时被调用。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td><code>onDispose</code> 在有完成、错误和取消时被调用。</td>
                                </tr>
                            </table>
                        </div>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_handle"><a class="anchor" href="#_handle"></a>4.4.3. Handle</h4>
                    <div class="paragraph">
                        <p><code>handle</code> 方法有些不同，它在 <code>Mono</code> 和 <code>Flux</code> 中都有。然而，它是一个实例方法
                            （instance method），意思就是它要链接在一个现有的源后使用（与其他操作符一样）。</p>
                    </div>
                    <div class="paragraph">
                        <p>它与 <code>generate</code> 比较类似，因为它也使用 <code>SynchronousSink</code>，并且只允许元素逐个发出。
                            然而，<code>handle</code> 可被用于基于现有数据源中的元素生成任意值，有可能还会跳过一些元素。
                            这样，可以把它当做 <code>map</code> 与 <code>filter</code> 的组合。<code>handle</code> 方法签名如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
                            <pre class="CodeRay highlight"><code data-lang="java">handle(BiConsumer&lt;T, SynchronousSink&lt;R&gt;&gt;)</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>举个例子，响应式流规范允许 <code>null</code> 这样的值出现在序列中。假如你想执行一个类似
                            <code>map</code> 的操作，你想利用一个现有的具有映射功能的方法，但是它会返回 null，这时候怎么办呢？</p>
                    </div>
                    <div class="paragraph">
                        <p>例如，下边的方法可以用于 Integer 序列，映射为字母或 null 。</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span
        class="predefined-type">String</span> alphabet(<span class="type">int</span> letterNumber) {
        <span class="keyword">if</span> (letterNumber &lt; <span class="integer">1</span> || letterNumber &gt; <span
            class="integer">26</span>) {
                <span class="keyword">return</span> <span class="predefined-constant">null</span>;
        }
        <span class="type">int</span> letterIndexAscii = <span class="string"><span class="delimiter">'</span><span
            class="content">A</span><span class="delimiter">'</span></span> + letterNumber - <span
            class="integer">1</span>;
        <span class="keyword">return</span> <span class="string"><span class="delimiter">&quot;</span><span
            class="delimiter">&quot;</span></span> + (<span class="type">char</span>) letterIndexAscii;
}</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>我们可以使用 <code>handle</code> 来去掉其中的 null。</p>
                    </div>
                    <div class="listingblock">
                        <div class="title">将 <code>handle</code> 用于一个 "映射 + 过滤 null" 的场景</div>
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; alphabet = Flux.just(-<span
        class="integer">1</span>, <span class="integer">30</span>, <span class="integer">13</span>, <span
        class="integer">9</span>, <span class="integer">20</span>)
    .handle((i, sink) -&gt; {
        <span class="predefined-type">String</span> letter = alphabet(i); <i class="conum" data-value="1"></i><b>(1)</b>
        <span class="keyword">if</span> (letter != <span class="predefined-constant">null</span>) <i class="conum"
                                                                                                     data-value="2"></i><b>(2)</b>
            sink.next(letter); <i class="conum" data-value="3"></i><b>(3)</b>
    });

alphabet.subscribe(<span class="predefined-type">System</span>.out::println);</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>映射到字母。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>如果返回的是 null &#8230;&#8203;</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>就不会调用 <code>sink.next</code> 从而过滤掉。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>输出如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>M
I
T</pre>
                        </div>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="schedulers"><a class="anchor" href="#schedulers"></a>4.5. 调度器（Schedulers）</h3>
                <div class="paragraph">
                    <p>Reactor， 就像 RxJava，也可以被认为是 <strong>并发无关（concurrency agnostic）</strong> 的。意思就是，
                        它并不强制要求任何并发模型。更进一步，它将选择权交给开发者。不过，它还是提供了一些方便
                        进行并发执行的库。</p>
                </div>
                <div class="paragraph">
                    <p>在 Reactor 中，执行模式以及执行过程取决于所使用的 <code>Scheduler</code>。
                        <a href="https://projectreactor.io/docs/core/release/api/reactor/core/scheduler/Scheduler.html"><code>Scheduler</code></a>
                        是一个拥有广泛实现类的抽象接口。
                        <a href="https://projectreactor.io/docs/core/release/api/reactor/core/scheduler/Schedulers.html"><code>Schedulers</code></a>
                        类提供的静态方法用于达成如下的执行环境：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>当前线程（<code>Schedulers.immediate()</code>）</p>
                        </li>
                        <li>
                            <p>可重用的单线程（<code>Schedulers.single()</code>）。注意，这个方法对所有调用者都提供同一个线程来使用，
                                直到该调度器（Scheduler）被废弃。如果你想使用专一的线程，就对每一个调用使用 <code>Schedulers.newSingle()</code>。</p>
                        </li>
                        <li>
                            <p>弹性线程池（<code>Schedulers.elastic()</code>。它根据需要创建一个线程池，重用空闲线程。线程池如果空闲时间过长
                                （默认为 60s）就会被废弃。对于 I/O 阻塞的场景比较适用。 <code>Schedulers.elastic()</code> 能够方便地给一个阻塞
                                的任务分配它自己的线程，从而不会妨碍其他任务和资源，见 <a href="#faq.wrap-blocking">如何包装一个同步阻塞的调用？</a>。</p>
                        </li>
                        <li>
                            <p>固定大小线程池（<code>Schedulers.parallel()</code>）。所创建线程池的大小与 CPU 个数等同。</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p>此外，你还可以使用 <code>Schedulers.fromExecutorService(ExecutorService)</code> 基于现有的
                        <code>ExecutorService</code> 创建 <code>Scheduler</code>。（虽然不太建议，不过你也可以使用 <code>Executor</code>
                        来创建）。你也可以使用 <code>newXXX</code> 方法来创建不同的调度器。比如
                        <code>Schedulers.newElastic(yourScheduleName)</code>
                        创建一个新的名为 <code>yourScheduleName</code> 的弹性调度器。</p>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                操作符基于非阻塞算法实现，从而可以利用到某些调度器的工作窃取（work stealing）
                                特性的好处。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>一些操作符默认会使用一个指定的调度器（通常也允许开发者调整为其他调度器）例如，
                        通过工厂方法 <code>Flux.interval(Duration.ofMillis(300))</code> 生成的每 300ms 打点一次的 <code>Flux&lt;Long&gt;</code>，
                        默认情况下使用的是 <code>Schedulers.parallel()</code>，下边的代码演示了如何将其装换为 <code>Schedulers.single()</code>：
                    </p>
                </div>
                <div class="listingblock">
                    <div class="content">
                        <pre class="CodeRay highlight"><code data-lang="java">Flux.interval(<span
                                class="predefined-type">Duration</span>.ofMillis(<span class="integer">300</span>), Schedulers.newSingle(<span
                                class="string"><span class="delimiter">&quot;</span><span
                                class="content">test</span><span class="delimiter">&quot;</span></span>))</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>Reactor 提供了两种在响应式链中调整调度器 <code>Scheduler</code> 的方法：<code>publishOn</code> 和
                        <code>subscribeOn</code>。
                        它们都接受一个 <code>Scheduler</code> 作为参数，从而可以改变调度器。但是 <code>publishOn</code> 在链中出现的位置
                        是有讲究的，而 <code>subscribeOn</code> 则无所谓。要理解它们的不同，你首先要理解
                        <a href="#reactive.subscribe">nothing happens until you subscribe()</a>。</p>
                </div>
                <div class="paragraph">
                    <p>在 Reactor 中，当你在操作链上添加操作符的时候，你可以根据需要在 <code>Flux</code> 和 <code>Mono</code>
                        的实现中包装其他的 <code>Flux</code> 和 <code>Mono</code>。一旦你订阅（subscribe）了它，一个 <code>Subscriber</code> 的链
                        就被创建了，一直向上到第一个 publisher 。这些对开发者是不可见的，开发者所能看到的是最外一层的
                        <code>Flux</code> （或 <code>Mono</code>）和 <code>Subscription</code>，但是具体的任务是在中间这些跟操作符相关的
                        subscriber 上处理的。</p>
                </div>
                <div class="paragraph">
                    <p>基于此，我们仔细研究一下 <code>publishOn</code> 和 <code>subscribeOn</code> 这两个操作符：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><code>publishOn</code> 的用法和处于订阅链（subscriber chain）中的其他操作符一样。它将上游
                                信号传给下游，同时执行指定的调度器 <code>Scheduler</code> 的某个工作线程上的回调。
                                它会 <strong>改变后续的操作符的执行所在线程</strong> （直到下一个 <code>publishOn</code>
                                出现在这个链上）。</p>
                        </li>
                        <li>
                            <p><code>subscribeOn</code> 用于订阅（subscription）过程，作用于那个向上的订阅链（发布者在被订阅
                                时才激活，订阅的传递方向是向上游的）。所以，无论你把 <code>subscribeOn</code> 至于操作链的什么位置，
                                <strong>它都会影响到源头的线程执行环境（context）</strong>。
                                但是，它不会影响到后续的 <code>publishOn</code>，后者仍能够切换其后操作符的线程执行环境。</p>
                        </li>
                    </ul>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                只有操作链中最早的 <code>subscribeOn</code> 调用才算数。
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="threading"><a class="anchor" href="#threading"></a>4.6. 线程模型</h3>
                <div class="paragraph">
                    <p><code>Flux</code> 和 <code>Mono</code> 不会创建线程。一些操作符，比如 <code>publishOn</code>，会创建线程。同时，作为一种任务共享形式，
                        这些操作符可能会从其他任务池（work pool）——如果其他任务池是空闲的话——那里“偷”线程。因此，
                        无论是 <code>Flux</code>、<code>Mono</code> 还是 <code>Subscriber</code> 都应该精于线程处理。它们依赖这些操作符来管理线程和任务池。
                    </p>
                </div>
                <div class="paragraph">
                    <p><code>publishOn</code> 强制下一个操作符（很可能包括下一个的下一个&#8230;&#8203;）来运行在一个不同的线程上。
                        类似的，<code>subscribeOn</code> 强制上一个操作符（很可能包括上一个的上一个&#8230;&#8203;）来运行在一个不同的线程上。
                        记住，在你订阅（subscribe）前，你只是定义了处理流程，而没有启动发布者。基于此，Reactor
                        可以使用这些规则来决定如何执行操作链。然后，一旦你订阅了，整个流程就开始工作了。</p>
                </div>
                <div class="paragraph">
                    <p>下边的例子演示了支持任务共享的多线程模型：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.range(<span class="integer">1</span>, <span class="integer">10000</span>) <i
        class="conum" data-value="1"></i><b>(1)</b>
    .publishOn(Schedulers.parallel()) <i class="conum" data-value="2"></i><b>(2)</b>
    .subscribe(result) <i class="conum" data-value="3"></i><b>(3)</b></code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>创建一个有 10,000 个元素的 <code>Flux</code>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>创建等同于 CPU 个数的线程（最小为4）。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td><a href="#reactive.subscribe"><code>subscribe()</code> 之前什么都不会发生</a>。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p><code>Scheduler.parallel()</code> 创建一个基于单线程 <code>ExecutorService</code> 的固定大小的任务线程池。
                        因为可能会有一个或两个线程导致问题，它总是至少创建 4 个线程。然后 publishOn 方法便共享了这些任务线程，
                        当 <code>publishOn</code> 请求元素的时候，会从任一个正在发出元素的线程那里获取元素。这样，
                        就是进行了任务共享（一种资源共享方式）。Reactor 还提供了好几种共享资源的方式，请参考
                        <a href="https://projectreactor.io/docs/core/release/api/reactor/core/scheduler/Schedulers.html">Schedulers</a>。
                    </p>
                </div>
                <div class="paragraph">
                    <p><code>Scheduler.elastic()</code> 也能创建线程，它能够很方便地创建专门的线程（以便跑一些可能会阻塞资源的任务，
                        比如一个同步服务），请见 <a href="#faq.wrap-blocking">如何包装一个同步阻塞的调用？</a>。</p>
                </div>
                <div class="paragraph">
                    <p>内部机制保证了这些操作符能够借助自增计数器（incremental counters）和警戒条件（guard conditions）
                        以线程安全的方式工作。例如，如果我们有四个线程处理一个流（就像上边的例子），每一个请求会让计数器自增，
                        这样后续的来自不同线程的请求就能拿到正确的元素。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="error.handling"><a class="anchor" href="#error.handling"></a>4.7. 处理错误</h3>
                <div class="admonitionblock tip">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-tip" title="Tip"></i>
                            </td>
                            <td class="content">
                                如果想了解有哪些可用于错误处理的操作符，请参考 <a href="#which.errors">the relevant operator decision tree</a>。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>在响应式流中，错误（error）是终止（terminal）事件。当有错误发生时，它会导致流序列停止，
                        并且错误信号会沿着操作链条向下传递，直至遇到你定义的 <code>Subscriber</code> 及其 <code>onError</code> 方法。</p>
                </div>
                <div class="paragraph">
                    <p>这样的错误还是应该在应用层面解决的。比如，你可能会将错误信息显示在用户界面，或者通过某个
                        REST 端点（endpoint）发出。因此，订阅者（subscriber）的 <code>onError</code> 方法是应该定义的。</p>
                </div>
                <div class="admonitionblock warning">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-warning" title="Warning"></i>
                            </td>
                            <td class="content">
                                如果没有定义，<code>onError</code> 会抛出 <code>UnsupportedOperationException</code>。你可以接下来再
                                检测错误，并通过 <code>Exceptions.isErrorCallbackNotImplemented</code> 方法捕获和处理它。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>Reactor 还提供了其他的用于在链中处理错误的方法，即错误处理操作（error-handling operators）。</p>
                </div>
                <div class="admonitionblock important">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-important" title="Important"></i>
                            </td>
                            <td class="content">
                                在你了解错误处理操作符之前，你必须牢记 <strong>响应式流中的任何错误都是一个终止事件</strong>。
                                即使用了错误处理操作符，也不会让源头流序列继续。而是将 <code>onError</code> 信号转化为一个 <strong>新的</strong> 序列
                                的开始。换句话说，它代替了被终结的 <em>上游</em> 流序列。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>现在我们来逐个看看错误处理的方法。需要的时候我们会同时用到命令式编程风格的 <code>try</code> 代码块来作比较。</p>
                </div>
                <div class="sect3">
                    <h4 id="_错误处理_方法"><a class="anchor" href="#_错误处理_方法"></a>4.7.1. “错误处理”方法</h4>
                    <div class="paragraph">
                        <p>你也许熟悉在 try-catch 代码块中处理异常的几种方法。常见的包括如下几种：</p>
                    </div>
                    <div class="olist arabic">
                        <ol class="arabic">
                            <li>
                                <p>捕获并返回一个静态的缺省值。</p>
                            </li>
                            <li>
                                <p>捕获并执行一个异常处理方法。</p>
                            </li>
                            <li>
                                <p>捕获并动态计算一个候补值来顶替。</p>
                            </li>
                            <li>
                                <p>捕获，并再包装为某一个 <code>业务相关的异常</code>，然后再抛出业务异常。</p>
                            </li>
                            <li>
                                <p>捕获，记录错误日志，然后继续抛出。</p>
                            </li>
                            <li>
                                <p>使用 <code>finally</code> 来清理资源，或使用 Java 7 引入的 "try-with-resource"。</p>
                            </li>
                        </ol>
                    </div>
                    <div class="paragraph">
                        <p>以上所有这些在 Reactor 都有相应的基于 error-handling 操作符处理方式。</p>
                    </div>
                    <div class="paragraph">
                        <p>在开始研究这些操作符之前，我们先准备好响应式链（reactive chain）方式和 try-catch 代码块方式（以便对比）。</p>
                    </div>
                    <div class="paragraph">
                        <p>当订阅的时候，位于链结尾的 <code>onError</code> 回调方法和 <code>catch</code> 块类似，一旦有异常，执行过程会跳入到 catch：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; s = Flux.range(<span
        class="integer">1</span>, <span class="integer">10</span>)
    .map(v -&gt; doSomethingDangerous(v)) <i class="conum" data-value="1"></i><b>(1)</b>
    .map(v -&gt; doSecondTransform(v)); <i class="conum" data-value="2"></i><b>(2)</b>
s.subscribe(value -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">RECEIVED </span><span
            class="delimiter">&quot;</span></span> + value), <i class="conum" data-value="3"></i><b>(3)</b>
            error -&gt; <span class="predefined-type">System</span>.err.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">CAUGHT </span><span
            class="delimiter">&quot;</span></span> + error) <i class="conum" data-value="4"></i><b>(4)</b>
);</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>执行 map 转换，有可能抛出异常。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>如果没问题，执行第二个 map 转换操作。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>所有转换成功的值都打印出来。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="4"></i><b>4</b></td>
                                <td>一旦有错误，序列（sequence）终止，并打印错误信息。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>这与 try/catch 代码块是类似的：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="keyword">try</span> {
    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="integer">1</span>; i &lt; <span
            class="integer">11</span>; i++) {
        <span class="predefined-type">String</span> v1 = doSomethingDangerous(i); <i class="conum"
                                                                                     data-value="1"></i><b>(1)</b>
        <span class="predefined-type">String</span> v2 = doSecondTransform(v1); <i class="conum" data-value="2"></i><b>(2)</b>
        <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">RECEIVED </span><span
            class="delimiter">&quot;</span></span> + v2);
    }
} <span class="keyword">catch</span> (<span class="predefined-type">Throwable</span> t) {
    <span class="predefined-type">System</span>.err.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">CAUGHT </span><span
            class="delimiter">&quot;</span></span> + t); <i class="conum" data-value="3"></i><b>(3)</b>
}</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>如果这里抛出异常&#8230;&#8203;</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>&#8230;&#8203;后续的代码跳过&#8230;&#8203;</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>&#8230;&#8203;执行过程直接到这。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>既然我们准备了两种方式做对比，我们就来看一下不同的错误处理场景，以及相应的操作符。</p>
                    </div>
                    <div class="sect4">
                        <h5 id="_静态缺省值"><a class="anchor" href="#_静态缺省值"></a>静态缺省值</h5>
                        <div class="paragraph">
                            <p>与第 <strong>(1)</strong> 条（捕获并返回一个静态的缺省值）对应的是 <code>onErrorReturn</code>：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.just(<span class="integer">10</span>)
    .map(<span class="local-variable">this</span>::doSomethingDangerous)
    .onErrorReturn(<span class="string"><span class="delimiter">&quot;</span><span class="content">RECOVERED</span><span
            class="delimiter">&quot;</span></span>);</code></pre>
                            </div>
                        </div>
                        <div class="paragraph">
                            <p>你还可以通过判断错误信息的内容，来筛选哪些要给出缺省值，哪些仍然让错误继续传递下去：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.just(<span class="integer">10</span>)
    .map(<span class="local-variable">this</span>::doSomethingDangerous)
    .onErrorReturn(e -&gt; e.getMessage().equals(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">boom10</span><span class="delimiter">&quot;</span></span>), <span class="string"><span
            class="delimiter">&quot;</span><span class="content">recovered10</span><span class="delimiter">&quot;</span></span>);</code></pre>
                            </div>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_异常处理方法"><a class="anchor" href="#_异常处理方法"></a>异常处理方法</h5>
                        <div class="paragraph">
                            <p>如果你不只是想要在发生错误的时候给出缺省值，而是希望提供一种更安全的处理数据的方式，
                                可以使用 <code>onErrorResume</code>。这与第 <strong>(2)</strong> 条（捕获并执行一个异常处理方法）类似。</p>
                        </div>
                        <div class="paragraph">
                            <p>假设，你会尝试从一个外部的不稳定服务获取数据，但仍然会在本地缓存一份 <strong>可能</strong> 有些过期的数据，
                                因为缓存的读取更加可靠。可以这样来做：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.just(<span class="string"><span
        class="delimiter">&quot;</span><span class="content">key1</span><span
        class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
        class="content">key2</span><span class="delimiter">&quot;</span></span>)
    .flatMap(k -&gt; callExternalService(k)) <i class="conum" data-value="1"></i><b>(1)</b>
    .onErrorResume(e -&gt; getFromCache(k)); <i class="conum" data-value="2"></i><b>(2)</b></code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>对于每一个 key， 异步地调用一个外部服务。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td>如果对外部服务的调用失败，则再去缓存中查找该 key。注意，这里无论 <code>e</code> 是什么，都会执行异常处理方法。</td>
                                </tr>
                            </table>
                        </div>
                        <div class="paragraph">
                            <p>就像 <code>onErrorReturn</code>，<code>onErrorResume</code> 也有可以用于预先过滤错误内容的方法变体，可以基于异常类或
                                <code>Predicate</code>
                                进行过滤。它实际上是用一个 <code>Function</code> 来作为参数，还可以返回一个新的流序列。</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.just(<span class="string"><span
        class="delimiter">&quot;</span><span class="content">timeout1</span><span class="delimiter">&quot;</span></span>, <span
        class="string"><span class="delimiter">&quot;</span><span class="content">unknown</span><span class="delimiter">&quot;</span></span>, <span
        class="string"><span class="delimiter">&quot;</span><span class="content">key2</span><span class="delimiter">&quot;</span></span>)
    .flatMap(k -&gt; callExternalService(k))
    .onErrorResume(error -&gt; { <i class="conum" data-value="1"></i><b>(1)</b>
        <span class="keyword">if</span> (error <span class="keyword">instanceof</span> <span class="exception">TimeoutException</span>) <i
            class="conum" data-value="2"></i><b>(2)</b>
            <span class="keyword">return</span> getFromCache(k);
        <span class="keyword">else</span> <span class="keyword">if</span> (error <span class="keyword">instanceof</span> UnknownKeyException)  <i
            class="conum" data-value="3"></i><b>(3)</b>
            <span class="keyword">return</span> registerNewEntry(k, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">DEFAULT</span><span
            class="delimiter">&quot;</span></span>);
        <span class="keyword">else</span>
            <span class="keyword">return</span> Flux.error(error); <i class="conum" data-value="4"></i><b>(4)</b>
    });</code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>这个函数式允许开发者自行决定如何处理。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td>如果源超时，使用本地缓存。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="3"></i><b>3</b></td>
                                    <td>如果源找不到对应的 key，创建一个新的实体。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="4"></i><b>4</b></td>
                                    <td>否则， 将问题“重新抛出”。</td>
                                </tr>
                            </table>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_动态候补值"><a class="anchor" href="#_动态候补值"></a>动态候补值</h5>
                        <div class="paragraph">
                            <p>有时候并不想提供一个错误处理方法，而是想在接收到错误的时候计算一个候补的值。这类似于第 <strong>(3)</strong>
                                条（捕获并动态计算一个候补值）。</p>
                        </div>
                        <div class="paragraph">
                            <p>例如，如果你的返回类型本身就有可能包装有异常（比如 <code>Future.complete(T success)</code> vs
                                <code>Future.completeExceptionally(Throwable error)</code>），你有可能使用流中的错误包装起来实例化
                                返回值。</p>
                        </div>
                        <div class="paragraph">
                            <p>这也可以使用上一种错误处理方法的方式（使用 <code>onErrorResume</code>）解决，代码如下：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">erroringFlux.onErrorResume(error -&gt; Mono.just( <i class="conum"
                                                                                                           data-value="1"></i><b>(1)</b>
        myWrapper.fromError(error) <i class="conum" data-value="2"></i><b>(2)</b>
));</code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>在 <code>onErrorResume</code> 中，使用 <code>Mono.just</code> 创建一个 <code>Mono</code>。
                                    </td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td>将异常包装到另一个类中。</td>
                                </tr>
                            </table>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_捕获并重新抛出"><a class="anchor" href="#_捕获并重新抛出"></a>捕获并重新抛出</h5>
                        <div class="paragraph">
                            <p>在“错误处理方法”的例子中，基于 <code>flatMap</code> 方法的最后一行，我们可以猜到如何做到第 <strong>(4)</strong>
                                条（捕获，包装到一个业务相关的异常，然后抛出业务异常）：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.just(<span class="string"><span
        class="delimiter">&quot;</span><span class="content">timeout1</span><span class="delimiter">&quot;</span></span>)
    .flatMap(k -&gt; callExternalService(k))
    .onErrorResume(original -&gt; Flux.error(
        <span class="keyword">new</span> BusinessException(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">oops, SLA exceeded</span><span
            class="delimiter">&quot;</span></span>, original)
    );</code></pre>
                            </div>
                        </div>
                        <div class="paragraph">
                            <p>然而还有一个更加直接的方法—— <code>onErrorMap</code>：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.just(<span class="string"><span
        class="delimiter">&quot;</span><span class="content">timeout1</span><span class="delimiter">&quot;</span></span>)
    .flatMap(k -&gt; callExternalService(k))
    .onErrorMap(original -&gt; <span class="keyword">new</span> BusinessException(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">oops, SLA exceeded</span><span
            class="delimiter">&quot;</span></span>, original));</code></pre>
                            </div>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_记录错误日志"><a class="anchor" href="#_记录错误日志"></a>记录错误日志</h5>
                        <div class="paragraph">
                            <p>如果对于错误你只是想在不改变它的情况下做出响应（如记录日志），并让错误继续传递下去，
                                那么可以用 <code>doOnError</code> 方法。这对应第 <strong>(5)</strong> 条（捕获，记录错误日志，并继续抛出）。
                                这个方法与其他以 <code>doOn</code> 开头的方法一样，只起副作用（"side-effect"）。它们对序列都是只读，
                                而不会带来任何改动。</p>
                        </div>
                        <div class="paragraph">
                            <p>如下边的例子所示，我们会记录错误日志，并且还通过变量自增统计错误发生个数。</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">LongAdder failureStat = <span class="keyword">new</span> LongAdder();
Flux&lt;<span class="predefined-type">String</span>&gt; flux =
Flux.just(<span class="string"><span class="delimiter">&quot;</span><span class="content">unknown</span><span
            class="delimiter">&quot;</span></span>)
    .flatMap(k -&gt; callExternalService(k)) <i class="conum" data-value="1"></i><b>(1)</b>
    .doOnError(e -&gt; {
        failureStat.increment();
        log(<span class="string"><span class="delimiter">&quot;</span><span class="content">uh oh, falling back, service failed for key </span><span
            class="delimiter">&quot;</span></span> + k); <i class="conum" data-value="2"></i><b>(2)</b>
    })
    .onErrorResume(e -&gt; getFromCache(k)); <i class="conum" data-value="3"></i><b>(3)</b></code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>对外部服务的调用失败&#8230;&#8203;</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td>&#8230;&#8203;记录错误日志&#8230;&#8203;</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="3"></i><b>3</b></td>
                                    <td>&#8230;&#8203;然后回调错误处理方法。</td>
                                </tr>
                            </table>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_使用资源和_try_catch_代码块"><a class="anchor" href="#_使用资源和_try_catch_代码块"></a>使用资源和 try-catch
                            代码块</h5>
                        <div class="paragraph">
                            <p>最后一个要与命令式编程对应的对比就是使用 Java 7 "try-with-resources" 或 <code>finally</code>
                                代码块清理资源。这是第 <strong>(6)</strong> 条（使用 <code>finally</code> 代码块清理资源或使用 Java 7 引入的
                                "try-with-resource"）。在 Reactor 中都有对应的方法： <code>using</code> 和 <code>doFinally</code>：
                            </p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span
        class="predefined-type">AtomicBoolean</span> isDisposed = <span class="keyword">new</span> <span
        class="predefined-type">AtomicBoolean</span>();
Disposable disposableInstance = <span class="keyword">new</span> Disposable() {
    <span class="annotation">@Override</span>
    <span class="directive">public</span> <span class="type">void</span> dispose() {
        isDisposed.set(<span class="predefined-constant">true</span>); <i class="conum" data-value="4"></i><b>(4)</b>
    }

    <span class="annotation">@Override</span>
    <span class="directive">public</span> <span class="predefined-type">String</span> toString() {
        <span class="keyword">return</span> <span class="string"><span class="delimiter">&quot;</span><span
            class="content">DISPOSABLE</span><span class="delimiter">&quot;</span></span>;
    }
};

Flux&lt;<span class="predefined-type">String</span>&gt; flux =
Flux.using(
        () -&gt; disposableInstance, <i class="conum" data-value="1"></i><b>(1)</b>
        disposable -&gt; Flux.just(disposable.toString()), <i class="conum" data-value="2"></i><b>(2)</b>
        Disposable::dispose <i class="conum" data-value="3"></i><b>(3)</b>
);</code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>第一个 lambda 生成资源，这里我们返回模拟的（mock） <code>Disposable</code>。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td>第二个 lambda 处理资源，返回一个 <code>Flux&lt;T&gt;</code>。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="3"></i><b>3</b></td>
                                    <td>第三个 lambda 在 2) 中的资源 <code>Flux</code> 终止或取消的时候，用于清理资源。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="4"></i><b>4</b></td>
                                    <td>在订阅或执行流序列之后， <code>isDisposed</code> 会置为 <code>true</code>。</td>
                                </tr>
                            </table>
                        </div>
                        <div class="paragraph">
                            <p>另一方面， <code>doFinally</code> 在序列终止（无论是 <code>onComplete</code>、`onError`还是取消）的时候被执行，
                                并且能够判断是什么类型的终止事件（完成、错误还是取消？）。</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">LongAdder statsCancel = <span class="keyword">new</span> LongAdder(); <i
        class="conum" data-value="1"></i><b>(1)</b>

Flux&lt;<span class="predefined-type">String</span>&gt; flux =
Flux.just(<span class="string"><span class="delimiter">&quot;</span><span class="content">foo</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">bar</span><span class="delimiter">&quot;</span></span>)
    .doFinally(type -&gt; {
        <span class="keyword">if</span> (type == SignalType.CANCEL) <i class="conum" data-value="2"></i><b>(2)</b>
          statsCancel.increment(); <i class="conum" data-value="3"></i><b>(3)</b>
    })
    .take(<span class="integer">1</span>); <i class="conum" data-value="4"></i><b>(4)</b></code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>我们想进行统计，所以用到了 <code>LongAdder</code>。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td><code>doFinally</code> 用 <code>SignalType</code> 检查了终止信号的类型。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="3"></i><b>3</b></td>
                                    <td>如果只是取消，那么统计数据自增。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="4"></i><b>4</b></td>
                                    <td><code>take(1)</code> 能够在发出 1 个元素后取消流。</td>
                                </tr>
                            </table>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_演示终止方法_code_onerror_code"><a class="anchor" href="#_演示终止方法_code_onerror_code"></a>演示终止方法
                            <code>onError</code></h5>
                        <div class="paragraph">
                            <p>为了演示当错误出现的时候如何导致上游序列终止，我们使用 <code>Flux.interval</code> 构造一个更加直观的例子。
                                这个 interval 操作符会在每 x 单位的时间发出一个自增的 <code>Long</code> 值。</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux =
Flux.interval(<span class="predefined-type">Duration</span>.ofMillis(<span class="integer">250</span>))
    .map(input -&gt; {
        <span class="keyword">if</span> (input &lt; <span class="integer">3</span>) <span class="keyword">return</span> <span
            class="string"><span class="delimiter">&quot;</span><span class="content">tick </span><span
            class="delimiter">&quot;</span></span> + input;
        <span class="keyword">throw</span> <span class="keyword">new</span> <span
            class="exception">RuntimeException</span>(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">boom</span><span class="delimiter">&quot;</span></span>);
    })
    .onErrorReturn(<span class="string"><span class="delimiter">&quot;</span><span class="content">Uh oh</span><span
            class="delimiter">&quot;</span></span>);

flux.subscribe(<span class="predefined-type">System</span>.out::println);
<span class="predefined-type">Thread</span>.sleep(<span class="integer">2100</span>); <i class="conum"
                                                                                         data-value="1"></i><b>(1)</b></code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>注意 <code>interval</code> 默认基于一个 <strong>timer</strong> <code>Scheduler</code>
                                        来执行。 如果我们想在 main 方法中运行，
                                        我们需要调用 <code>sleep</code>，这样程序就可以避免在还没有产生任何值的时候就退出了。
                                    </td>
                                </tr>
                            </table>
                        </div>
                        <div class="paragraph">
                            <p>每 250ms 打印出一行信息，如下：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre>tick 0
tick 1
tick 2
Uh oh</pre>
                            </div>
                        </div>
                        <div class="paragraph">
                            <p>即使多给了 1 秒钟时间，也没有更多的 tick 信号由 <code>interval</code> 产生了，所以序列确实被错误信号终止了。</p>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_重试"><a class="anchor" href="#_重试"></a>重试</h5>
                        <div class="paragraph">
                            <p>还有一个用于错误处理的操作符你可能会用到，就是 <code>retry</code>，见文知意，用它可以对出现错误的序列进行重试。</p>
                        </div>
                        <div class="paragraph">
                            <p>问题是它对于上游 <code>Flux</code> 是基于重订阅（<strong>re-subscribing</strong>）的方式。这实际上已经一个不同的序列了，
                                发出错误信号的序列仍然是终止了的。为了验证这一点，我们可以在继续用上边的例子，增加一个 <code>retry(1)</code>
                                代替 <code>onErrorReturn</code> 来重试一次。</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.interval(<span class="predefined-type">Duration</span>.ofMillis(<span
        class="integer">250</span>))
    .map(input -&gt; {
        <span class="keyword">if</span> (input &lt; <span class="integer">3</span>) <span class="keyword">return</span> <span
            class="string"><span class="delimiter">&quot;</span><span class="content">tick </span><span
            class="delimiter">&quot;</span></span> + input;
        <span class="keyword">throw</span> <span class="keyword">new</span> <span
            class="exception">RuntimeException</span>(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">boom</span><span class="delimiter">&quot;</span></span>);
    })
    .elapsed() <i class="conum" data-value="1"></i><b>(1)</b>
    .retry(<span class="integer">1</span>)
    .subscribe(<span class="predefined-type">System</span>.out::println, <span class="predefined-type">System</span>.err::println); <i
            class="conum" data-value="2"></i><b>(2)</b>

<span class="predefined-type">Thread</span>.sleep(<span class="integer">2100</span>); <i class="conum"
                                                                                         data-value="3"></i><b>(3)</b></code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td><code>elapsed</code> 会关联从当前值与上个值发出的时间间隔（译者加：如下边输出的内容中的 259/249/251&#8230;&#8203;）。
                                    </td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td>我们还是要看一下 <code>onError</code> 时的内容。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="3"></i><b>3</b></td>
                                    <td>确保我们有足够的时间可以进行 4x2 次 tick。</td>
                                </tr>
                            </table>
                        </div>
                        <div class="paragraph">
                            <p>输出如下：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre>259,tick 0
249,tick 1
251,tick 2
506,tick 0 <i class="conum" data-value="1"></i><b>(1)</b>
248,tick 1
253,tick 2
java.lang.RuntimeException: boom</pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>一个新的 <code>interval</code> 从 tick 0 开始。多出来的 250ms 间隔来自于第 4 次 tick，
                                        就是导致出现异常并执行 retry 的那次（译者加：我在机器上测试的时候 <code>elapsed</code>
                                        “显示”的时间间隔没有加倍，但是确实有第 4 次的间隔）。
                                    </td>
                                </tr>
                            </table>
                        </div>
                        <div class="paragraph">
                            <p>可见， <code>retry(1)</code> 不过是再一次从新订阅了原始的 <code>interval</code>，从 tick 0 开始。第二次，
                                由于异常再次出现，便将异常传递到下游了。</p>
                        </div>
                        <div class="paragraph">
                            <p>还有一个“高配版”的 <code>retry</code> （<code>retryWhen</code>），它使用一个伴随（"companion"）
                                <code>Flux</code>
                                来判断对某次错误是否要重试。这个伴随 <code>Flux</code> 是由操作符创建的，但是由开发者包装它，
                                从而实现对重试操作的配置。</p>
                        </div>
                        <div class="paragraph">
                            <p>这个伴随 <code>Flux</code> 是一个 <code>Flux&lt;Throwable&gt;</code>，它作为 <code>retryWhen</code>
                                的唯一参数被传递给一个
                                <code>Function</code>，你可以定义这个 <code>Function</code> 并让它返回一个新的
                                <code>Publisher&lt;?&gt;</code>。重试的循环
                                会这样运行：</p>
                        </div>
                        <div class="olist arabic">
                            <ol class="arabic">
                                <li>
                                    <p>每次出现错误，错误信号会发送给伴随 <code>Flux</code>，后者已经被你用 <code>Function</code> 包装。</p>
                                </li>
                                <li>
                                    <p>如果伴随 <code>Flux</code> 发出元素，就会触发重试。</p>
                                </li>
                                <li>
                                    <p>如果伴随 <code>Flux</code> 完成（complete），重试循环也会停止，并且原始序列也会
                                        <strong>完成（complete）</strong>。</p>
                                </li>
                                <li>
                                    <p>如果伴随 <code>Flux</code> 产生一个错误，重试循环停止，原始序列也停止 <strong>或</strong> 完成，并且这个错误会导致
                                        原始序列失败并终止。</p>
                                </li>
                            </ol>
                        </div>
                        <div class="paragraph">
                            <p>了解前两个场景的区别是很重要的。如果让伴随 <code>Flux</code> 完成（complete）等于吞掉了错误。如下代码用
                                <code>retryWhen</code> 模仿了 <code>retry(3)</code> 的效果：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux = Flux
    .&lt;<span class="predefined-type">String</span>&gt;error(<span class="keyword">new</span> <span class="exception">IllegalArgumentException</span>()) <i
            class="conum" data-value="1"></i><b>(1)</b>
    .doOnError(<span class="predefined-type">System</span>.out::println) <i class="conum" data-value="2"></i><b>(2)</b>
    .retryWhen(companion -&gt; companion.take(<span class="integer">3</span>)); <i class="conum" data-value="3"></i><b>(3)</b></code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>持续产生错误。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td>在 retry <strong>之前</strong> 的 <code>doOnError</code> 可以让我们看到错误。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="3"></i><b>3</b></td>
                                    <td>这里，我们认为前 3 个错误是可以重试的（<code>take(3)</code>），再有错误就放弃。</td>
                                </tr>
                            </table>
                        </div>
                        <div class="paragraph">
                            <p>事实上，上边例子最终得到的是一个 <strong>空的</strong> <code>Flux</code>，但是却 <strong>成功</strong> 完成了。反观对同一个
                                <code>Flux</code>
                                调用 <code>retry(3)</code> 的话，最终是以最后一个 error 终止 <code>Flux</code>，故而
                                <code>retryWhen</code> 与之不同。</p>
                        </div>
                        <div class="paragraph">
                            <p>实现同样的效果需要一些额外的技巧：</p>
                        </div>
                        <div class="listingblock">
                            <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux =
Flux.&lt;<span class="predefined-type">String</span>&gt;error(<span class="keyword">new</span> <span class="exception">IllegalArgumentException</span>())
    .retryWhen(companion -&gt; companion
    .zipWith(Flux.range(<span class="integer">1</span>, <span class="integer">4</span>), <i class="conum"
                                                                                            data-value="1"></i><b>(1)</b>
          (error, index) -&gt; { <i class="conum" data-value="2"></i><b>(2)</b>
            <span class="keyword">if</span> (index &lt; <span class="integer">4</span>) <span
            class="keyword">return</span> index; <i class="conum" data-value="3"></i><b>(3)</b>
            <span class="keyword">else</span> <span class="keyword">throw</span> Exceptions.propagate(error); <i
            class="conum" data-value="4"></i><b>(4)</b>
          })
    );</code></pre>
                            </div>
                        </div>
                        <div class="colist arabic">
                            <table>
                                <tr>
                                    <td><i class="conum" data-value="1"></i><b>1</b></td>
                                    <td>技巧一：使用 <code>zip</code> 和一个“重试个数 + 1”的 <code>range</code>。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="2"></i><b>2</b></td>
                                    <td><code>zip</code> 方法让你可以在对重试次数计数的同时，仍掌握着原始的错误（error）。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="3"></i><b>3</b></td>
                                    <td>允许三次重试，小于 4 的时候发出一个值。</td>
                                </tr>
                                <tr>
                                    <td><i class="conum" data-value="4"></i><b>4</b></td>
                                    <td>为了使序列以错误结束。我们将原始异常在三次重试之后抛出。</td>
                                </tr>
                            </table>
                        </div>
                        <div class="admonitionblock tip">
                            <table>
                                <tr>
                                    <td class="icon">
                                        <i class="fa icon-tip" title="Tip"></i>
                                    </td>
                                    <td class="content">
                                        类似的代码也可以被用于实现 <em>exponential backoff and retry</em> 模式
                                        （译者加：重试指定的次数, 且每一次重试之间停顿的时间逐渐增加），参考 <a href="#faq.exponentialBackoff">FAQ</a>。
                                    </td>
                                </tr>
                            </table>
                        </div>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_在操作符或函数式中处理异常"><a class="anchor" href="#_在操作符或函数式中处理异常"></a>4.7.2. 在操作符或函数式中处理异常</h4>
                    <div class="paragraph">
                        <p>总体来说，所有的操作符自身都可能包含触发异常的代码，或自定义的可能导致失败的代码，
                            所以它们都自带一些错误处理方式。</p>
                    </div>
                    <div class="paragraph">
                        <p>一般来说，一个 <strong>不受检异常（Unchecked Exception）</strong> 总是由 <code>onError</code> 传递。例如，
                            在一个 <code>map</code> 方法中抛出 <code>RuntimeException</code> 会被翻译为一个 <code>onError</code> 事件，如下：
                        </p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.just(<span class="string"><span
        class="delimiter">&quot;</span><span class="content">foo</span><span class="delimiter">&quot;</span></span>)
    .map(s -&gt; { <span class="keyword">throw</span> <span class="keyword">new</span> <span class="exception">IllegalArgumentException</span>(s); })
    .subscribe(v -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">GOT VALUE</span><span
            class="delimiter">&quot;</span></span>),
               e -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">ERROR: </span><span
            class="delimiter">&quot;</span></span> + e));</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>上边代码输出如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
                            <pre>ERROR: java.lang.IllegalArgumentException: foo</pre>
                        </div>
                    </div>
                    <div class="admonitionblock tip">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-tip" title="Tip"></i>
                                </td>
                                <td class="content">
                                    <code>Exception</code> 可以在其被传递给 <code>onError</code> 之前，使用 <a
                                        href="#hooks-internal">hook</a> 进行调整。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>Reactor，定义了一系列的能够导致“严重失败”的错误（比如 <code>OutOfMemoryError</code>），也可参考
                            <code>Exceptions.throwIfFatal</code> 方法。这些错误意味着 Reactor 无力处理只能抛出，无法传递下去。</p>
                    </div>
                    <div class="admonitionblock note">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-note" title="Note"></i>
                                </td>
                                <td class="content">
                                    还有些情况下不受检异常仍然无法传递下去（多数处于subscribe 和 request 阶段），
                                    因为可能由于多线程竞争导致两次 <code>onError</code> 或 <code>onComplete</code> 的情况。当这种竞争发生的时候，
                                    无法传递下去的错误信号就被“丢弃”了。这些情况仍然可以通过自定义的 hook 来搞定，见
                                    <a href="#hooks-dropping">丢弃事件的 Hooks</a>。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>你可能会问：“那么 <strong>受检查异常（Checked Exceptions）</strong>？”</p>
                    </div>
                    <div class="paragraph">
                        <p>如果你需要调用一个声明为 <code>throws</code> 异常的方法，你仍然需要使用 <code>try-catch</code> 代码块处理异常。
                            有几种方式：</p>
                    </div>
                    <div class="olist arabic">
                        <ol class="arabic">
                            <li>
                                <p>捕获异常，并修复它，流序列正常继续。</p>
                            </li>
                            <li>
                                <p>捕获异常，并把它包装（wrap）到一个 <em>不受检异常</em> 中，然后抛出（中断序列）。工具类 <code>Exceptions</code>
                                    可用于这种方式（我们马上会讲到）。</p>
                            </li>
                            <li>
                                <p>如果你期望返回一个 <code>Flux</code> （例如在 <code>flatMap</code> 中），将异常包装在一个产生错误的 <code>Flux`中：
                                    `return Flux.error(checkedException)</code>（流序列也会终止）。</p>
                            </li>
                        </ol>
                    </div>
                    <div class="paragraph">
                        <p>Reactor 有一个工具类 <code>Exceptions</code>，可以确保在收到受检异常的时候将其包装（wrap）起来。</p>
                    </div>
                    <div class="ulist">
                        <ul>
                            <li>
                                <p>如果需要，可以使用 <code>Exceptions.propagate</code> 方法来包装异常，它同样会首先调用
                                    <code>throwIfFatal</code>，
                                    并且不会包装 <code>RuntimeException</code>。</p>
                            </li>
                            <li>
                                <p>使用 <code>Exceptions.unwrap</code> 方法来得到原始的未包装的异常（追溯最初的异常）。</p>
                            </li>
                        </ul>
                    </div>
                    <div class="paragraph">
                        <p>下面是一个 <code>map</code> 的例子，它使用的 convert 方法会抛出 <code>IOException</code>：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> <span
        class="predefined-type">String</span> convert(<span class="type">int</span> i) <span
        class="directive">throws</span> <span class="exception">IOException</span> {
    <span class="keyword">if</span> (i &gt; <span class="integer">3</span>) {
        <span class="keyword">throw</span> <span class="keyword">new</span> <span
            class="exception">IOException</span>(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">boom </span><span class="delimiter">&quot;</span></span> + i);
    }
    <span class="keyword">return</span> <span class="string"><span class="delimiter">&quot;</span><span class="content">OK </span><span
            class="delimiter">&quot;</span></span> + i;
}</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>现在想象你将这个方法用于一个 <code>map</code> 中，你必须明确捕获这个异常，并且你的 <code>map</code> 方法不能再次抛出它。
                            所以你可以将其以 <code>RuntimeException</code> 的形式传递给 <code>onError</code>：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; converted = Flux
    .range(<span class="integer">1</span>, <span class="integer">10</span>)
    .map(i -&gt; {
        <span class="keyword">try</span> { <span class="keyword">return</span> convert(i); }
        <span class="keyword">catch</span> (<span class="exception">IOException</span> e) { <span
            class="keyword">throw</span> Exceptions.propagate(e); }
    });</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>当后边订阅上边的这个 <code>Flux</code> 并响应错误（比如在用户界面）的时候，如果你想处理 IOException，
                            你还可以再将其转换为原始的异常。如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">converted.subscribe(
    v -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">RECEIVED: </span><span class="delimiter">&quot;</span></span> + v),
    e -&gt; {
        <span class="keyword">if</span> (Exceptions.unwrap(e) <span class="keyword">instanceof</span> <span
            class="exception">IOException</span>) {
            <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Something bad happened with I/O</span><span
            class="delimiter">&quot;</span></span>);
        } <span class="keyword">else</span> {
            <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Something bad happened</span><span class="delimiter">&quot;</span></span>);
        }
    }
);</code></pre>
                        </div>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="processors"><a class="anchor" href="#processors"></a>4.8. Processors</h3>
                <div class="paragraph">
                    <p>Processors 既是一种特别的发布者（<code>Publisher</code>）又是一种订阅者（<code>Subscriber</code>）。
                        那意味着你可以
                        订阅一个 <code>Processor</code>（通常它们会实现 <code>Flux</code>），也可以调用相关方法来手动
                        插入数据到序列，或终止序列。</p>
                </div>
                <div class="paragraph">
                    <p>Processor 有多种类型，它们都有特别的语义规则，但是在你研究它们之前，最好问一下
                        自己如下几个问题：</p>
                </div>
                <div class="sect3">
                    <h4 id="_我是否需要使用_processor"><a class="anchor" href="#_我是否需要使用_processor"></a>4.8.1. 我是否需要使用
                        Processor？</h4>
                    <div class="paragraph">
                        <p>多数情况下，你应该进行避免使用 <code>Processor</code>，它们较难正确使用，主要用于一些特殊场景下。</p>
                    </div>
                    <div class="paragraph">
                        <p>如果你觉得 <code>Processor</code> 适合你的使用场景，请首先看一下是否尝试过以下两种替代方式：</p>
                    </div>
                    <div class="olist arabic">
                        <ol class="arabic">
                            <li>
                                <p>是否有一个或多个操作符的组合能够满足需求？（见 <a href="#which-operator">我需要哪个操作符？</a>）</p>
                            </li>
                            <li>
                                <p><a href="#producing">"generator"</a> 操作符是否能解决问题？（通常这些操作符
                                    可以用来桥接非响应式的 API，它们提供了一个“sink”，在生成数据流序列方面，
                                    概念上类似于 <code>Processor</code>）</p>
                            </li>
                        </ol>
                    </div>
                    <div class="paragraph">
                        <p>如果看了以上替代方案，你仍然觉得需要一个 <code>Processor</code>，阅读 <a href="#processor-overview">现有的 Processors
                            总览</a>
                            这一节来了解一下不同的实现吧。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_使用_code_sink_code_门面对象来线程安全地生成流"><a class="anchor"
                                                                 href="#_使用_code_sink_code_门面对象来线程安全地生成流"></a>4.8.2. 使用
                        <code>Sink</code> 门面对象来线程安全地生成流</h4>
                    <div class="paragraph">
                        <p>比起直接使用 Reactor 的 <code>Processors</code>，更好的方式是通过调用一次 <code>sink()</code>
                            来得到 <code>Processor</code> 的 <code>Sink</code>。</p>
                    </div>
                    <div class="paragraph">
                        <p><code>FluxProcessor</code> 的 sink 是线程安全的“生产者（producer）”，因此能够在应用程序中
                            多线程并发地生成数据。例如，一个线程安全的序列化（serialized）的 sink 能够通过
                            <code>UnicastProcessor</code> 创建：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">UnicastProcessor&lt;<span class="predefined-type">Integer</span>&gt; processor = UnicastProcessor.create();
FluxSink&lt;<span class="predefined-type">Integer</span>&gt; sink = processor.sink(overflowStrategy);</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>多个生产者线程可以并发地生成数据到以下的序列化 sink。</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
                            <pre class="CodeRay highlight"><code data-lang="java">sink.next(n);</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>根据 <code>Processor</code> 及其配置，<code>next</code> 产生的溢出有两种可能的处理方式：</p>
                    </div>
                    <div class="ulist">
                        <ul>
                            <li>
                                <p>一个无限的 processor 通过丢弃或缓存自行处理溢出。</p>
                            </li>
                            <li>
                                <p>一个有限的 processor 阻塞在 <code>IGNORE</code> 策略，或将 <code>overflowStrategy</code>
                                    应用于 <code>sink</code>。</p>
                            </li>
                        </ul>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="processor-overview"><a class="anchor" href="#processor-overview"></a>4.8.3. 现有的 Processors
                        总览</h4>
                    <div class="paragraph">
                        <p>Reactor Core 内置多种 <code>Processor</code>。这些 processor 具有不同的语法，大概分为三类。
                            下边简要介绍一下这三种 processor：</p>
                    </div>
                    <div class="ulist">
                        <ul>
                            <li>
                                <p><strong>直接的（direct）</strong> （<code>DirectProcessor</code> 和
                                    <code>UnicastProcessor</code>）：这些 processors 只能通过直接
                                    调用 <code>Sink</code> 的方法来推送数据。</p>
                            </li>
                            <li>
                                <p><strong>同步的（synchronous）</strong> （<code>EmitterProcessor</code> 和 <code>ReplayProcessor</code>）：这些
                                    processors 既可以
                                    直接调用 <code>Sink</code> 方法来推送数据，也可以通过订阅到一个上游的发布者来同步地产生数据。</p>
                            </li>
                            <li>
                                <p><strong>异步的（asynchronous）</strong> （<code>WorkQueueProcessor</code> 和 <code>TopicProcessor</code>）：这些
                                    processors
                                    可以将从多个上游发布者得到的数据推送下去。由于使用了 <code>RingBuffer</code> 的数据结构来
                                    缓存多个来自上游的数据，因此更加有健壮性。</p>
                            </li>
                        </ul>
                    </div>
                    <div class="paragraph">
                        <p>异步的 processor 在实例化的时候最复杂，因为有许多不同的选项。因此它们暴露出一个 <code>Builder</code> 接口。
                            而简单的 processors 有静态的工厂方法。</p>
                    </div>
                    <div class="sect4">
                        <h5 id="_directprocessor"><a class="anchor" href="#_directprocessor"></a>DirectProcessor</h5>
                        <div class="paragraph">
                            <p><code>DirectProcessor</code> 可以将信号分发给零到多个订阅者（<code>Subscriber</code>）。它是最容易实例化的，使用静态方法
                                <code>create()</code> 即可。另一方面，<strong>它的不足是无法处理背压</strong>。所以，当
                                <code>DirectProcessor</code> 推送的是 N
                                个元素，而至少有一个订阅者的请求个数少于 N 的时候，就会发出一个 <code>IllegalStateException</code>。</p>
                        </div>
                        <div class="paragraph">
                            <p>一旦 <code>Processor</code> 终止（通常通过调用它的 <code>Sink</code> 的 <code>error(Throwable)</code> 或
                                <code>complete()</code> 方法），
                                虽然它允许更多的订阅者订阅它，但是会立即向它们重新发送终止信号。</p>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_unicastprocessor"><a class="anchor" href="#_unicastprocessor"></a>UnicastProcessor</h5>
                        <div class="paragraph">
                            <p><code>UnicastProcessor</code> 可以使用一个内置的缓存来处理背压。代价就是它最多只能有一个订阅者。</p>
                        </div>
                        <div class="paragraph">
                            <p><code>UnicastProcessor</code> 有多种选项，因此提供多种不同的 <code>create</code> 静态方法。例如，它默认是
                                <em>无限的（unbounded）</em> ：如果你在在订阅者还没有请求数据的情况下让它推送数据，它会缓存所有数据。</p>
                        </div>
                        <div class="paragraph">
                            <p>可以通过提供一个自定义的 <code>Queue</code> 的具体实现传递给 <code>create</code> 工厂方法来改变默认行为。如果给出的队列是
                                有限的（bounded）， 并且缓存已满，而且未收到下游的请求，processor 会拒绝推送数据。</p>
                        </div>
                        <div class="paragraph">
                            <p>在上边 <em>有限的</em> 例子中，还可以在构造 processor 的时候提供一个回调方法，这个回调方法可以在每一个
                                被拒绝推送的元素上调用，从而让开发者有机会清理这些元素。</p>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_emitterprocessor"><a class="anchor" href="#_emitterprocessor"></a>EmitterProcessor</h5>
                        <div class="paragraph">
                            <p><code>EmitterProcessor</code> 能够向多个订阅者发送数据，并且可以对每一个订阅者进行背压处理。它本身也可以订阅一个
                                <code>Publisher</code> 并同步获得数据。</p>
                        </div>
                        <div class="paragraph">
                            <p>最初如果没有订阅者，它仍然允许推送一些数据到缓存，缓存大小由 <code>bufferSize</code> 定义。
                                之后如果仍然没有订阅者订阅它并消费数据，对 <code>onNext</code> 的调用会阻塞，直到有订阅者接入
                                （这时只能并发地订阅了）。</p>
                        </div>
                        <div class="paragraph">
                            <p>因此第一个订阅者会收到最多 <code>bufferSize</code> 个元素。然而之后， processor 不会重新发送（replay）
                                数据给后续的订阅者。这些后续接入的订阅者只能获取到它们开始订阅 <strong>之后</strong> 推送的数据。这个内部的
                                缓存会继续用于背压的目的。</p>
                        </div>
                        <div class="paragraph">
                            <p>默认情况下，如果所有的订阅者都取消了（基本意味着它们都不再订阅（un-subscribed）了），
                                它会清空内部缓存，并且不再接受更多的订阅者。这一点可以通过 <code>create</code> 静态工厂方法的
                                <code>autoCancel</code> 参数来配置。</p>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_replayprocessor"><a class="anchor" href="#_replayprocessor"></a>ReplayProcessor</h5>
                        <div class="paragraph">
                            <p><code>ReplayProcessor</code> 会缓存直接通过自身的 <code>Sink</code> 推送的元素，以及来自上游发布者的元素，
                                并且后来的订阅者也会收到重发（replay）的这些元素。</p>
                        </div>
                        <div class="paragraph">
                            <p>可以通过多种配置方式创建它：</p>
                        </div>
                        <div class="ulist">
                            <ul>
                                <li>
                                    <p>缓存一个元素（<code>cacheLast</code>）。</p>
                                </li>
                                <li>
                                    <p>缓存一定个数的历史元素（<code>create(int)</code>），所有的历史元素（<code>create()</code>）。</p>
                                </li>
                                <li>
                                    <p>缓存基于时间窗期间内的元素（<code>createTimeout(Duration)</code>）。</p>
                                </li>
                                <li>
                                    <p>缓存基于历史个数和时间窗的元素（<code>createSizeOrTimeout(int, Duration)</code>）。</p>
                                </li>
                            </ul>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_topicprocessor"><a class="anchor" href="#_topicprocessor"></a>TopicProcessor</h5>
                        <div class="paragraph">
                            <p><code>TopicProcessor</code> 是一个异步的 processor，它能够重发来自多个上游发布者的元素，
                                这需要在创建它的时候配置 <code>shared</code> （见 <code>build()</code> 的 <code>share(boolean)</code>
                                配置）。</p>
                        </div>
                        <div class="paragraph">
                            <p>注意，如果你企图在并发环境下通过并发的上游 Publisher 调用 <code>TopicProcessor</code> 的 <code>onNext</code>、
                                <code>onComplete</code>，或 <code>onError</code> 方法，就必须配置 shared。</p>
                        </div>
                        <div class="paragraph">
                            <p>否则，并发调用就是非法的，从而 processor 是完全兼容响应式流规范的。</p>
                        </div>
                        <div class="paragraph">
                            <p><code>TopicProcessor</code> 能够对多个订阅者发送数据。它通过对每一个订阅者关联一个线程来实现这一点，
                                这个线程会一直执行直到 processor 发出 <code>onError</code> 或 <code>onComplete</code> 信号，或关联的订阅者被取消。
                                最多可以接受的订阅者个数由构造者方法 <code>executor</code> 指定，通过提供一个有限线程数的 <code>ExecutorService</code>
                                来限制这一个数。</p>
                        </div>
                        <div class="paragraph">
                            <p>这个 processor 基于一个 <code>RingBuffer</code> 数据结构来存储已发送的数据。每一个订阅者线程
                                自行管理其相关的数据在 <code>RingBuffer</code> 中的索引。</p>
                        </div>
                        <div class="paragraph">
                            <p>这个 processor 也有一个 <code>autoCancel</code> 构造器方法：如果设置为 <code>true</code> （默认的），那么当
                                所有的订阅者取消之后，源 <code>Publisher</code>(s) 也就被取消了。</p>
                        </div>
                    </div>
                    <div class="sect4">
                        <h5 id="_workqueueprocessor"><a class="anchor" href="#_workqueueprocessor"></a>WorkQueueProcessor
                        </h5>
                        <div class="paragraph">
                            <p><code>WorkQueueProcessor</code> 也是一个异步的 processor，也能够重发来自多个上游发布者的元素，
                                同样在创建时需要配置 <code>shared</code> （它多数构造器配置与 <code>TopicProcessor</code> 相同）。</p>
                        </div>
                        <div class="paragraph">
                            <p>它放松了对响应式流规范的兼容，但是好处就在于相对于 <code>TopicProcessor</code> 来说需要更少的资源。
                                它仍然基于 <code>RingBuffer</code>，但是不再要求每一个订阅者都关联一个线程，因此相对于 <code>TopicProcessor</code>
                                来说更具扩展性。</p>
                        </div>
                        <div class="paragraph">
                            <p>代价在于分发模式有些区别：来自订阅者的请求会汇总在一起，并且这个 processor 每次只对一个
                                订阅者发送数据，因此需要循环（round-robin）对订阅者发送数据，而不是一次全部发出的模式。</p>
                        </div>
                        <div class="admonitionblock note">
                            <table>
                                <tr>
                                    <td class="icon">
                                        <i class="fa icon-note" title="Note"></i>
                                    </td>
                                    <td class="content">
                                        无法保证完全公平的循环分发。
                                    </td>
                                </tr>
                            </table>
                        </div>
                        <div class="paragraph">
                            <p><code>WorkQueueProcessor</code> 多数构造器方法与 <code>TopicProcessor</code> 相同，比如 <code>autoCancel</code>、<code>share</code>，
                                以及 <code>waitStrategy</code>。下游订阅者的最大数目同样由构造器 <code>executor</code> 配置的 <code>ExecutorService</code>
                                决定。</p>
                        </div>
                        <div class="admonitionblock warning">
                            <table>
                                <tr>
                                    <td class="icon">
                                        <i class="fa icon-warning" title="Warning"></i>
                                    </td>
                                    <td class="content">
                                        你最好注意不要有太多订阅者订阅 <code>WorkQueueProcessor</code>，因为这 <strong>会锁住
                                        processor</strong>。
                                        如果你需要限制订阅者数量，最好使用一个 <code>ThreadPoolExecutor</code> 或 <code>ForkJoinPool</code>。这个
                                        processor 能够检测到（线程池）容量并在订阅者过多时抛出异常。
                                    </td>
                                </tr>
                            </table>
                        </div>
                        <div class="paragraph">
                            <p><a class="fa fa-edit"
                                  href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/coreFeatures.adoc"
                                  title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                                - "<a href="#core-features">Reactor 核心特性</a>"</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="kotlin"><a class="anchor" href="#kotlin"></a>5. 对 Kotlin 的支持</h2>
        <div class="sectionbody">
            <div class="sect2">
                <h3 id="kotlin-introduction"><a class="anchor" href="#kotlin-introduction"></a>5.1. 简介</h3>
                <div class="paragraph">
                    <p><a href="https://kotlinlang.org">Kotlin</a> 是一种运行于 JVM（及其他平台）上的静态（statically-typed）语言。
                        使用它可以在拥有与现有 Java 库良好https://kotlinlang.org/docs/reference/java-interop.html[互操作性]
                        的同时编写简介优雅的代码。</p>
                </div>
                <div class="paragraph">
                    <p>本小节介绍了 Reactor 3.1 如何能够完美支持 Kotlin。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="kotlin-requirements"><a class="anchor" href="#kotlin-requirements"></a>5.2. 前提</h3>
                <div class="paragraph">
                    <p>Kotlin 支持 Kotlin 1.1+ 及依赖
                        <a href="https://bintray.com/bintray/jcenter/org.jetbrains.kotlin%3Akotlin-stdlib"><code>kotlin-stdlib</code></a>
                        （或
                        <a href="https://bintray.com/bintray/jcenter/org.jetbrains.kotlin%3Akotlin-stdlib-jre7"><code>kotlin-stdlib-jre7</code></a>
                        / <a href="https://bintray.com/bintray/jcenter/org.jetbrains.kotlin%3Akotlin-stdlib-jre8"><code>kotlin-stdlib-jre8</code></a>
                        之一）</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="kotlin-extensions"><a class="anchor" href="#kotlin-extensions"></a>5.3. 扩展</h3>
                <div class="paragraph">
                    <p>多亏了其良好的 <a href="https://kotlinlang.org/docs/reference/java-interop.html">Java 互操作性</a>
                        以及 <a href="https://kotlinlang.org/docs/reference/extensions.html">Kotlin 扩展（extensions）</a>,
                        Reactor
                        Kotlin APIs 既可使用 Java APIs，还能够收益于一些 Reactor 内置的专门支持 Kotlin 的 APIs。</p>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                <div class="paragraph">
                                    <p>注意 Kotlin 的扩展需要 import 才能够使用。所以比如 <code>Throwable.toFlux</code> 的 Kotlin
                                        扩展必须在 <code>import reactor.core.publisher.toFlux</code> 后才可使用。多数场景下 IDE
                                        应该能够自动给出这种类似 static import 的建议。</p>
                                </div>
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>例如，https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters[Kotlin
                        参数类型推导（reified type parameters）]
                        对于 JVM 的 <a href="https://docs.oracle.com/javase/tutorial/java/generics/erasure.html">通用类型擦除（generics
                            type erasure）</a>提供了一种变通解决方案，
                        Reactor 就可以通过扩展（extension）来应用到这种特性。</p>
                </div>
                <div class="paragraph">
                    <p>下面是对“Reactor with Java”和“Reactor with Kotlin + extensions”的比较：</p>
                </div>
                <table class="tableblock frame-all grid-all spread">
                    <colgroup>
                        <col style="width: 50%;">
                        <col style="width: 50%;">
                    </colgroup>
                    <tbody>
                    <tr>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Java</strong></p>
                        </td>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Kotlin +
                            extensions</strong></p></td>
                    </tr>
                    <tr>
                        <td class="tableblock halign-left valign-top"><p class="tableblock">
                            <code>Mono.just("foo")</code></p></td>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>"foo".toMono()</code>
                        </p></td>
                    </tr>
                    <tr>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Flux.fromIterable(list)</code>
                        </p></td>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>list.toFlux()</code>
                        </p></td>
                    </tr>
                    <tr>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Mono.error(new
                            RuntimeException())</code></p></td>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>RuntimeException().toMono()</code>
                        </p></td>
                    </tr>
                    <tr>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>Flux.error(new
                            RuntimeException())</code></p></td>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>RuntimeException().toFlux()</code>
                        </p></td>
                    </tr>
                    <tr>
                        <td class="tableblock halign-left valign-top"><p class="tableblock">
                            <code>flux.ofType(Foo.class)</code></p></td>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>flux.ofType&lt;Foo&gt;()</code>
                            or <code>flux.ofType(Foo::class)</code></p></td>
                    </tr>
                    <tr>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>StepVerifier.create(flux).verifyComplete()</code>
                        </p></td>
                        <td class="tableblock halign-left valign-top"><p class="tableblock"><code>flux.test().verifyComplete()</code>
                        </p></td>
                    </tr>
                    </tbody>
                </table>
                <div class="paragraph">
                    <p>可参考 <a href="https://projectreactor.io/docs/core/release/kdoc-api/">Reactor KDoc API</a> 中详细的关于
                        Kotlin 扩展的文档。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="kotlin-null-safety"><a class="anchor" href="#kotlin-null-safety"></a>5.4. Null 值安全</h3>
                <div class="paragraph">
                    <p>Kotlin的一个关键特性就是 <a href="https://kotlinlang.org/docs/reference/null-safety.html">null 值安全</a>
                        ——从而可以在编译时处理 <code>null</code> 值，而不是在运行时抛出著名的 <code>NullPointerException</code>。
                        这样，通过“可能为空（nullability）”的声明，以及能够表明“有值或空值”的语法（避免使用类似
                        <code>Optional</code> 来进行包装），使得应用程序更加安全。（Kotlin允许在函数参数中使用可能为空的值，
                        请参考 <a href="http://www.baeldung.com/kotlin-null-safety">comprehensive guide to Kotlin
                            null-safety</a>）</p>
                </div>
                <div class="paragraph">
                    <p>尽管 Java 的类型系统不允许这样的 null 值安全的表达方式， Reactor <a href="#null-safety">now
                        provides null-safety</a> 对所有 Reactor API 通过工具友好的（tooling-friendly）注解（在
                        <code>reactor.util.annotation</code> 包中定义）来支持。
                        默认情况下，Java APIs 用于 Kotlin 的话会被作为
                        <a href="https://kotlinlang.org/docs/reference/java-interop.html#null-safety-and-platform-types">平台类型（platform
                            types）</a>
                        而放松对 null 的检查。
                        <a href="https://github.com/Kotlin/KEEP/blob/jsr-305/proposals/jsr-305-custom-nullability-qualifiers.md">Kotlin
                            对 JSR 305 注解的支持</a>
                        + Reactor 可为空（nullability）的注解，为所有 Reactor API 和 Kotlin 开发者确保“null 值安全”的特性
                        （在编译期处理 null 值）。</p>
                </div>
                <div class="paragraph">
                    <p>JSR 305 的检查可以通过增加 <code>-Xjsr305</code> 编译参数进行配置： <code>-Xjsr305={strict|warn|ignore}</code>。</p>
                </div>
                <div class="paragraph">
                    <p>对于 kotlin 1.1.50+，默认的配置为 <code>-Xjsr305=warn</code>。如果希望 Reactor API 能够全面支持 null 值安全
                        则需要配置为 <code>strict</code>。不过你可以认为这是实验性的（experimental），因为 Reactor API “可能为空”
                        的声明可能甚至在小版本的发布中都会不断改进，而且将来也可能增加新的检查。</p>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                <div class="paragraph">
                                    <p>目前尚不支持通用类型参数、可变类型以及数组元素的“可为空”。不过应该包含在接下来的发布中，最新信息请看
                                        <a href="https://github.com/Kotlin/KEEP/issues/79">这个issues</a>。</p>
                                </div>
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p><a class="fa fa-edit"
                          href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/kotlin.adoc"
                          title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                        - "<a href="#kotlin">对 Kotlin 的支持</a>"</p>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="testing"><a class="anchor" href="#testing"></a>6. 测试</h2>
        <div class="sectionbody">
            <div class="paragraph">
                <p>无论你是编写了一个简单的 Reactor 操作链，还是开发了自定义的操作符，对它进行
                    自动化的测试总是一个好主意。</p>
            </div>
            <div class="paragraph">
                <p>Reactor 内置一些专门用于测试的元素，放在一个专门的 artifact 里： <code>reactor-test</code>。
                    你可以在 <a href="https://github.com/reactor/reactor-core/tree/master/reactor-test/src">on Github</a>
                    的 <em>reactor-core</em> 库里找到这个项目。</p>
            </div>
            <div class="paragraph">
                <p>如果要用它来进行测试，添加 scope 为 test 的依赖。</p>
            </div>
            <div class="listingblock">
                <div class="title">reactor-test 用 Maven 配置 <code>&lt;dependencies&gt;</code></div>
                <div class="content">
<pre class="CodeRay highlight"><code data-lang="xml"><span class="tag">&lt;dependency&gt;</span>
    <span class="tag">&lt;groupId&gt;</span>io.projectreactor<span class="tag">&lt;/groupId&gt;</span>
    <span class="tag">&lt;artifactId&gt;</span>reactor-test<span class="tag">&lt;/artifactId&gt;</span>
    <span class="tag">&lt;scope&gt;</span>test<span class="tag">&lt;/scope&gt;</span>
    <i class="conum" data-value="1"></i><b>(1)</b>
<span class="tag">&lt;/dependency&gt;</span></code></pre>
                </div>
            </div>
            <div class="colist arabic">
                <table>
                    <tr>
                        <td><i class="conum" data-value="1"></i><b>1</b></td>
                        <td>如果你使用了 <a href="#getting">BOM</a>，你不需要指定 <code>&lt;version&gt;</code>。</td>
                    </tr>
                </table>
            </div>
            <div class="listingblock">
                <div class="title">reactor-test 用 Gradle 配置 <code>dependencies</code></div>
                <div class="content">
<pre class="CodeRay highlight"><code data-lang="groovy">dependencies {
   testCompile <span class="string"><span class="delimiter">'</span><span
            class="content">io.projectreactor:reactor-test</span><span class="delimiter">'</span></span>
}</code></pre>
                </div>
            </div>
            <div class="paragraph">
                <p><code>reactor-test</code> 的两个主要用途：</p>
            </div>
            <div class="ulist">
                <ul>
                    <li>
                        <p>使用 <code>StepVerifier</code> 一步一步地测试一个给定场景的序列。</p>
                    </li>
                    <li>
                        <p>使用 <code>TestPublisher</code> 生成数据来测试下游的操作符（包括你自己的operator）。</p>
                    </li>
                </ul>
            </div>
            <div class="sect2">
                <h3 id="_使用_code_stepverifier_code_来测试"><a class="anchor" href="#_使用_code_stepverifier_code_来测试"></a>6.1.
                    使用 <code>StepVerifier</code> 来测试</h3>
                <div class="paragraph">
                    <p>最常见的测试 Reactor 序列的场景就是定义一个 <code>Flux</code> 或 <code>Mono</code>，然后在订阅它的时候测试它的行为。</p>
                </div>
                <div class="paragraph">
                    <p>当你的测试关注于每一次的事件的时候，就非常容易转化为使用 <code>StepVerifier</code> 的测试场景：
                        下一个期望的事件是什么？你是否期望使用 <code>Flux</code> 来发出一个特别的值？或者接下来 300ms
                        什么都不做？所有这些都可以使用 <code>StepVerifier</code> API 来表示。</p>
                </div>
                <div class="paragraph">
                    <p>例如，你可能会使用如下的工具方法来包装一个 <code>Flux</code>：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> &lt;T&gt; Flux&lt;T&gt; appendBoomError(Flux&lt;T&gt; source) {
  <span class="keyword">return</span> source.concatWith(Mono.error(<span class="keyword">new</span> <span
            class="exception">IllegalArgumentException</span>(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">boom</span><span class="delimiter">&quot;</span></span>)));
}</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>要测试它的话，你需要校验如下内容：</p>
                </div>
                <div class="quoteblock">
                    <blockquote>
                        <div class="paragraph">
                            <p>我希望这个 <code>Flux</code> 先发出 <code>foo</code>，然后发出 <code>bar</code>，然后 <strong>生成一个内容为
                                <code>boom</code> 的错误</strong>。
                                最后订阅并校验它们。</p>
                        </div>
                    </blockquote>
                </div>
                <div class="paragraph">
                    <p>使用 <code>StepVerifier</code> API 来表示以上的验证过程：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="annotation">@Test</span>
<span class="directive">public</span> <span class="type">void</span> testAppendBoomError() {
  Flux&lt;<span class="predefined-type">String</span>&gt; source = Flux.just(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">foo</span><span class="delimiter">&quot;</span></span>, <span
            class="string"><span class="delimiter">&quot;</span><span class="content">bar</span><span class="delimiter">&quot;</span></span>); <i
            class="conum" data-value="1"></i><b>(1)</b>

  StepVerifier.create( <i class="conum" data-value="2"></i><b>(2)</b>
    appendBoomError(source)) <i class="conum" data-value="3"></i><b>(3)</b>
    .expectNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">foo</span><span
            class="delimiter">&quot;</span></span>) <i class="conum" data-value="4"></i><b>(4)</b>
    .expectNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">bar</span><span
            class="delimiter">&quot;</span></span>)
    .expectErrorMessage(<span class="string"><span class="delimiter">&quot;</span><span class="content">boom</span><span
            class="delimiter">&quot;</span></span>) <i class="conum" data-value="5"></i><b>(5)</b>
    .verify(); <i class="conum" data-value="6"></i><b>(6)</b>
}</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>由于被测试方法需要一个 <code>Flux</code>，定义一个简单的 <code>Flux</code> 用于测试。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>创建一个 <code>StepVerifier</code> 构造器来包装和校验一个 <code>Flux</code>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>传进来需要测试的 <code>Flux</code>（即待测方法的返回结果）。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>第一个我们期望的信号是 <code>onNext</code>，它的值为 <code>foo</code>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="5"></i><b>5</b></td>
                            <td>最后我们期望的是一个终止信号 <code>onError</code>，异常内容应该为 <code>boom</code>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="6"></i><b>6</b></td>
                            <td>不要忘了使用 <code>verify()</code> 触发测试。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>API 是一个构造器，通过传入一个要测试的序列来创建一个 <code>StepVerifier</code>。从而你可以：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>表示你 <em>期望</em> 发生的下一个信号。如果收到其他信号（或者信号与期望不匹配），整个测试就会
                                失败（<code>AssertionError</code>）。例如你可能会用到 <code>expectNext(T...)</code> 或 <code>expectNextCount(long)</code>。
                            </p>
                        </li>
                        <li>
                            <p><em>消费</em> 下一个信号。当你想要跳过部分序列或者当你想对信号内容进行自定义的 <code>assertion</code>
                                的时候会用到它（比如要校验是否有一个 <code>onNext</code> 信号，并校验对应发出的元素是否是一个 size 为
                                5 的 List）。你可能会用到 <code>consumeNextWith(Consumer&lt;T&gt;)</code>。</p>
                        </li>
                        <li>
                            <p><em>更多样的操作</em> 比如暂停或运行一段代码。比如，你想对测试状态或内容进行调整或处理，
                                你可能会用到 <code>thenAwait(Duration)</code> 和 <code>then(Runnable)</code>。</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p>对于终止事件，相应的期望方法（<code>expectComplete()</code>、<code>expectError()</code>，及其所有的变体方法）
                        使用之后就不能再继续增加别的期望方法了。最后你只能对 <code>StepVerifier</code> 进行一些额外的配置并
                        <strong>触发校验</strong>（通常调用 <code>verify()</code> 及其变体方法）。</p>
                </div>
                <div class="paragraph">
                    <p>从 StepVerifier 内部来看，它订阅了待测试的 <code>Flux</code> 或 <code>Mono</code>，然后将序列中的每个信号与测试
                        场景的期望进行比对。如果匹配的话，测试成功。如果有不匹配的情况，则抛出 <code>AssertionError</code> 异常。</p>
                </div>
                <div class="admonitionblock important">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-important" title="Important"></i>
                            </td>
                            <td class="content">
                                请记住是 <code>verify()</code> 触发了校验过程。这个 API 还有一些结合了 <code>verify()</code> 与期望的终止信号
                                的方法：<code>verifyComplete()</code>、<code>verifyError()</code>、<code>verifyErrorMessage(String)</code>
                                等。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>注意，如果有一个传入 lambda 的期望方法抛出了 <code>AssertionError</code>，会被报告为测试失败。
                        这可用于自定义 assertion。</p>
                </div>
                <div class="admonitionblock tip">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-tip" title="Tip"></i>
                            </td>
                            <td class="content">
                                默认情况下，<code>verify()</code> 方法（及同源的 <code>verifyThenAssertThat</code>、<code>verifyComplete()`等）
                                没有超时的概念。它可能会永远阻塞住。你可以使用 `StepVerifier.setDefaultTimeout(Duration)</code>
                                来设置一个全局的超时时间，或使用 <code>verify(Duration)</code> 指定。
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_操控时间"><a class="anchor" href="#_操控时间"></a>6.2. 操控时间</h3>
                <div class="paragraph">
                    <p><code>StepVerifier</code> 可以用来测试基于时间的操作符，从而避免测试的长时间运行。可以使用构造器
                        <code>StepVerifier.withVirtualTime</code> 达到这一点。</p>
                </div>
                <div class="paragraph">
                    <p>示例如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">StepVerifier.withVirtualTime(() -&gt; Mono.delay(<span
        class="predefined-type">Duration</span>.ofDays(<span class="integer">1</span>)))
<span class="comment">//... 继续追加期望方法</span></code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p><strong>虚拟时间（virtual time）</strong> 的功能会在 Reactor 的调度器（<code>Scheduler</code>）工厂方法中插入一个自定义的
                        调度器。这些基于时间的操作符通常默认使用 <code>Schedulers.parallel()</code> 调度器。（虚拟时间的）
                        技巧在于使用一个 <code>VirtualTimeScheduler</code> 来代替默认调度器。然而一个重要的前提就是，只有在初始化
                        虚拟时间调度器之后的操作符才会起作用。</p>
                </div>
                <div class="paragraph">
                    <p>为了提高 <code>StepVerifier</code> 正常起作用的概率，它一般不接收一个简单的 <code>Flux</code> 作为输入，而是接收
                        一个 <code>Supplier</code>，从而可以在配置好订阅者 <strong>之后</strong> “懒创建”待测试的 flux。</p>
                </div>
                <div class="admonitionblock important">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-important" title="Important"></i>
                            </td>
                            <td class="content">
                                要注意的是，<code>Supplier&lt;Publisher&lt;T&gt;&gt;</code> 可用于“懒创建”，否则不能保证虚拟时间
                                能完全起作用。尤其要避免提前实例化 <code>Flux</code>，要在 <code>Supplier</code> 中用 lambda 创建并返回
                                <code>Flux</code> 变量。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>有两种处理时间的期望方法，无论是否配置虚拟时间都是可用的：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><code>thenAwait(Duration)</code> 暂停校验步骤（允许信号延迟发出）。</p>
                        </li>
                        <li>
                            <p><code>expectNoEvent(Duration)</code> 同样让序列持续一定的时间，期间如果有 <strong>任何</strong> 信号发出则测试失败。
                            </p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p>两个方法都会基于给定的持续时间暂停线程的执行，如果是在虚拟时间模式下就相应地使用虚拟时间。</p>
                </div>
                <div class="admonitionblock tip" id="tip-expectNoEvent">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-tip" title="Tip"></i>
                            </td>
                            <td class="content">
                                <code>expectNoEvent</code> 将订阅（<code>subscription</code>）也认作一个事件。假设你用它作为第一步，如果检测
                                到有订阅信号，也会失败。这时候可以使用 <code>expectSubscription().expectNoEvent(duration)</code> 来代替。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>为了快速校验前边提到的 <code>Mono.delay</code>，我们可以这样完成代码：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">StepVerifier.withVirtualTime(() -&gt; Mono.delay(<span
        class="predefined-type">Duration</span>.ofDays(<span class="integer">1</span>)))
    .expectSubscription() <i class="conum" data-value="1"></i><b>(1)</b>
    .expectNoEvent(<span class="predefined-type">Duration</span>.ofDays(<span class="integer">1</span>)) <i
            class="conum" data-value="2"></i><b>(2)</b>
    .expectNext(<span class="integer">0</span>) <i class="conum" data-value="3"></i><b>(3)</b>
    .verifyComplete(); <i class="conum" data-value="4"></i><b>(4)</b></code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>如上 <a href="#tip-expectNoEvent">tip</a>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>期待一天内没有信号发生。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>然后期待一个 next 信号为 <code>0</code>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>然后期待完成（同时触发校验）。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>我们也可以使用 <code>thenAwait(Duration.ofDays(1))</code>，但是 <code>expectNoEvent</code> 的好处是
                        能够验证在此之前不会发生什么。</p>
                </div>
                <div class="paragraph">
                    <p>注意 <code>verify()</code> 返回一个 <code>Duration</code>，这是整个测试的 <strong>真实时间</strong>。</p>
                </div>
                <div class="admonitionblock warning">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-warning" title="Warning"></i>
                            </td>
                            <td class="content">
                                虚拟时间并非银弹。请记住 <em>所有的</em> 调度器都会被替换为 <code>VirtualTimeScheduler</code>。
                                有些时候你可以锁定校验过程，因为虚拟时钟在遇到第一个期望校验之前并不会开始，所以对于
                                “无数据“的期望校验也必须能够运行在虚拟时间模式下。在无限序列中，虚拟时间模式的发挥
                                空间也很有限，因为它可能导致线程（序列的发出和校验的运行都在这个线程上）卡住。
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_使用_code_stepverifier_code_进行_后校验"><a class="anchor"
                                                              href="#_使用_code_stepverifier_code_进行_后校验"></a>6.3. 使用
                    <code>StepVerifier</code> 进行“后校验”</h3>
                <div class="paragraph">
                    <p>当配置完你测试场景的最后的期望方法后，你可以使用 <code>verifyThenAssertThat()</code> 来代替
                        <code>verify()</code> 触发执行后的校验。</p>
                </div>
                <div class="paragraph">
                    <p><code>verifyThenAssertThat()</code> 返回一个 <code>StepVerifier.Assertions</code> 对象，你可以用它来校验
                        整个测试场景成功刚结束后的一些状态（<strong>它也会调用 <code>verify()</code></strong>）。典型应用就是校验有多少
                        元素被操作符丢弃（参考 <a href="#hooks">Hooks</a>）。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_测试_code_context_code"><a class="anchor" href="#_测试_code_context_code"></a>6.4. 测试
                    <code>Context</code></h3>
                <div class="paragraph">
                    <p>更多关于 <code>Context</code> 的内容请参考 <a href="#context">增加一个 Context 到响应式序列</a>。</p>
                </div>
                <div class="paragraph">
                    <p><code>StepVerifier</code> 有一些期望方法可以用来测试 <code>Context</code>：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><code>expectAccessibleContext</code>: 返回一个 <code>ContextExpectations</code> 对象，你可以用它来在
                                <code>Context</code>
                                上配置期望校验。一定记住要调用 <code>then()</code> 来返回到对序列的期望校验上来。</p>
                        </li>
                        <li>
                            <p><code>expectNoAccessibleContext</code>: 是对“没有`Context`”的校验。通常用于
                                被测试的 <code>Publisher</code> 并不是一个响应式的，或没有任何操作符能够传递 <code>Context</code>
                                （比如一个 <code>generate</code> 的 <code>Publisher</code>）.</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p>此外，还可以用 <code>StepVerifierOptions</code> 方法传入一个测试用的初始 <code>Context</code> 给
                        <code>StepVerifier</code>，
                        从而可以创建一个校验（verifier）。</p>
                </div>
                <div class="paragraph">
                    <p>这些特性通过下边的代码演示：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">StepVerifier.create(Mono.just(<span class="integer">1</span>).map(i -&gt; i + <span
        class="integer">10</span>),
                                StepVerifierOptions.create().withInitialContext(<span
            class="predefined-type">Context</span>.of(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">foo</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">bar</span><span class="delimiter">&quot;</span></span>))) <i
            class="conum" data-value="1"></i><b>(1)</b>
                            .expectAccessibleContext() <i class="conum" data-value="2"></i><b>(2)</b>
                            .contains(<span class="string"><span class="delimiter">&quot;</span><span class="content">foo</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">bar</span><span class="delimiter">&quot;</span></span>) <i class="conum" data-value="3"></i><b>(3)</b>
                            .then() <i class="conum" data-value="4"></i><b>(4)</b>
                            .expectNext(<span class="integer">11</span>)
                            .verifyComplete(); <i class="conum" data-value="5"></i><b>(5)</b></code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>使用 <code>StepVerifierOptions</code> 创建 <code>StepVerifier</code> 并传入初始
                                <code>Context</code>。
                            </td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>开始对 <code>Context</code> 进行校验，这里只是确保 <code>Context</code> 正常传播了。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>对 <code>Context</code> 进行校验的例子：比如验证是否包含一个 "foo" - "bar" 键值对。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>使用 <code>then()</code> 切换回对序列的校验。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="5"></i><b>5</b></td>
                            <td>不要忘了用 <code>verify()</code> 触发整个校验过程。</td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_用_code_testpublisher_code_手动发出元素"><a class="anchor"
                                                              href="#_用_code_testpublisher_code_手动发出元素"></a>6.5. 用
                    <code>TestPublisher</code> 手动发出元素</h3>
                <div class="paragraph">
                    <p>对于更多高级的测试，如果能够完全掌控源发出的数据就会方便很多，因为这样就可以在测试的
                        时候更加有的放矢地发出想测的数据。</p>
                </div>
                <div class="paragraph">
                    <p>另一种情况就是你实现了自己的操作符，然后想校验它的行为——尤其是在源不稳定的时候——是否符合响应式流规范。</p>
                </div>
                <div class="paragraph">
                    <p><code>reactor-test</code> 提供了 <code>TestPublisher</code> 类来应对这两种需求。这个类本质上是一个 <code>Publisher&lt;T&gt;</code>，
                        你可以通过可编程的方式来用它发出各种信号：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><code>next(T)</code> 以及 <code>next(T, T...)</code> 发出 1-n 个 <code>onNext</code> 信号。</p>
                        </li>
                        <li>
                            <p><code>emit(T...)</code> 起同样作用，并且会执行 <code>complete()</code>。</p>
                        </li>
                        <li>
                            <p><code>complete()</code> 会发出终止信号 <code>onComplete</code>。</p>
                        </li>
                        <li>
                            <p><code>error(Throwable)</code> 会发出终止信号 <code>onError</code>。</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p>使用 <code>create</code> 工厂方法就可以得到一个正常的 <code>TestPublisher</code>。而使用
                        <code>createNonCompliant</code>
                        工厂方法可以创建一个“不正常”的 <code>TestPublisher</code>。后者需要传入由 <code>TestPublisher.Violation</code>
                        枚举指定的一组选项，这些选项可用于告诉 publisher 忽略哪些问题。枚举值有：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><code>REQUEST_OVERFLOW</code>: 允许 <code>next</code> 在请求不足的时候也可以调用，而不会触发 <code>IllegalStateException</code>。
                            </p>
                        </li>
                        <li>
                            <p><code>ALLOW_NULL</code>: 允许 <code>next</code> 能够发出一个 <code>null</code> 值而不会触发 <code>NullPointerException</code>。
                            </p>
                        </li>
                        <li>
                            <p><code>CLEANUP_ON_TERMINATE</code>: 可以重复多次发出终止信号，包括
                                <code>complete()</code>、<code>error()</code> 和 <code>emit()</code>。</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p>最后，<code>TestPublisher</code> 还可以用不同的 <code>assert*</code> 来跟踪其内部的订阅状态。</p>
                </div>
                <div class="paragraph">
                    <p>使用转换方法 <code>flux()</code> 和 <code>mono()</code> 可以将其作为 <code>Flux</code> 和 <code>Mono</code>
                        来使用。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_用_code_publisherprobe_code_检查执行路径"><a class="anchor"
                                                               href="#_用_code_publisherprobe_code_检查执行路径"></a>6.6. 用
                    <code>PublisherProbe</code> 检查执行路径</h3>
                <div class="paragraph">
                    <p>当构建复杂的操作链时，可能会有多个子序列，从而导致多个执行路径。</p>
                </div>
                <div class="paragraph">
                    <p>多数时候，这些子序列会生成一个足够明确的 <code>onNext</code> 信号，你可以通过检查最终结果
                        来判断它是否执行。</p>
                </div>
                <div class="paragraph">
                    <p>考虑下边这个方法，它构建了一条操作链，并使用 <code>switchIfEmpty</code> 方法在源为空的情况下，
                        替换成另一个源。</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> Flux&lt;<span
        class="predefined-type">String</span>&gt; processOrFallback(Mono&lt;<span class="predefined-type">String</span>&gt; source, Publisher&lt;<span
        class="predefined-type">String</span>&gt; fallback) {
    <span class="keyword">return</span> source
            .flatMapMany(phrase -&gt; Flux.fromArray(phrase.split(<span class="string"><span
            class="delimiter">&quot;</span><span class="char">\\</span><span class="content">s+</span><span
            class="delimiter">&quot;</span></span>)))
            .switchIfEmpty(fallback);
}</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>很容易就可以测试出 switchIfEmpty 的哪一个逻辑分支被使用了，如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="annotation">@Test</span>
<span class="directive">public</span> <span class="type">void</span> testSplitPathIsUsed() {
    StepVerifier.create(processOrFallback(Mono.just(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">just a  phrase with    tabs!</span><span class="delimiter">&quot;</span></span>),
            Mono.just(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">EMPTY_PHRASE</span><span class="delimiter">&quot;</span></span>)))
                .expectNext(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">just</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">a</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">phrase</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">with</span><span class="delimiter">&quot;</span></span>, <span
            class="string"><span class="delimiter">&quot;</span><span class="content">tabs!</span><span
            class="delimiter">&quot;</span></span>)
                .verifyComplete();
}

<span class="annotation">@Test</span>
<span class="directive">public</span> <span class="type">void</span> testEmptyPathIsUsed() {
    StepVerifier.create(processOrFallback(Mono.empty(), Mono.just(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">EMPTY_PHRASE</span><span
            class="delimiter">&quot;</span></span>)))
                .expectNext(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">EMPTY_PHRASE</span><span class="delimiter">&quot;</span></span>)
                .verifyComplete();
}</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>但是如果例子中的方法返回的是一个 <code>Mono&lt;Void&gt;</code> 呢？它等待源发送结束，执行一个额外的任务，
                        然后就结束了。如果源是空的，则执行另一个备用的类似于 Runnable 的任务，如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="directive">private</span> Mono&lt;<span
        class="predefined-type">String</span>&gt; executeCommand(<span class="predefined-type">String</span> command) {
    <span class="keyword">return</span> Mono.just(command + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> DONE</span><span
            class="delimiter">&quot;</span></span>);
}

<span class="directive">public</span> Mono&lt;<span
            class="predefined-type">Void</span>&gt; processOrFallback(Mono&lt;<span
            class="predefined-type">String</span>&gt; commandSource, Mono&lt;<span class="predefined-type">Void</span>&gt; doWhenEmpty) {
    <span class="keyword">return</span> commandSource
            .flatMap(command -&gt; executeCommand(command).then()) <i class="conum" data-value="1"></i><b>(1)</b>
            .switchIfEmpty(doWhenEmpty); <i class="conum" data-value="2"></i><b>(2)</b>
}</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td><code>then()</code> 方法会忽略 command，它只关心是否结束。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>两个都是空序列，这个时候如何区分（哪边执行了）呢？</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>为了验证执行路径是经过了 <code>doWhenEmpty</code> 的，你需要编写额外的代码，比如你需要一个这样的
                        <code>Mono&lt;Void&gt;</code>：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>能够捕获到它被订阅的事实。</p>
                        </li>
                        <li>
                            <p>以上事实需要在整个执行结束 <strong>之后</strong> 再进行验证。</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p>在 3.1 版本以前，你需要为每一种状态维护一个 <code>AtomicBoolean</code> 变量，然后在相应的 <code>doOn*</code>
                        回调中观察它的值。这需要添加不少的额外代码。好在，版本 3.1.0 之后可以使用 `PublisherProbe`来做，
                        如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="annotation">@Test</span>
<span class="directive">public</span> <span class="type">void</span> testCommandEmptyPathIsUsed() {
    PublisherProbe&lt;<span class="predefined-type">Void</span>&gt; probe = PublisherProbe.empty(); <i class="conum"
                                                                                                       data-value="1"></i><b>(1)</b>

    StepVerifier.create(processOrFallback(Mono.empty(), probe.mono())) <i class="conum" data-value="2"></i><b>(2)</b>
                .verifyComplete();

    probe.assertWasSubscribed(); <i class="conum" data-value="3"></i><b>(3)</b>
    probe.assertWasRequested(); <i class="conum" data-value="4"></i><b>(4)</b>
    probe.assertWasNotCancelled(); <i class="conum" data-value="5"></i><b>(5)</b>
}</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>创建一个探针（probe），它会转化为一个空序列。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>在需要使用 <code>Mono&lt;Void&gt;</code> 的位置调用 <code>probe.mono()</code> 来替换为探针。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>序列结束之后，你可以用这个探针来判断序列是如何使用的，你可以检查是它从哪（条路径）被订阅的&#8230;&#8203;</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>&#8230;&#8203;对于请求也是一样的&#8230;&#8203;</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="5"></i><b>5</b></td>
                            <td>&#8230;&#8203;以及是否被取消了。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>你也可以在使用 <code>Flux&lt;T&gt;</code> 的位置通过调用 <code>.flux()</code> 方法来放置探针。如果你既需要用探针检查执行路径
                        还需要它能够发出数据，你可以用 <code>PublisherProbe.of(Publisher)</code> 方法包装一个 <code>Publisher&lt;T&gt;</code>
                        来搞定。</p>
                </div>
                <div class="paragraph">
                    <p><a class="fa fa-edit"
                          href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/testing.adoc"
                          title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                        - "<a href="#testing">测试</a>"</p>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="debugging"><a class="anchor" href="#debugging"></a>7. 调试 Reactor</h2>
        <div class="sectionbody">
            <div class="paragraph">
                <p>从命令式和同步式编程切换到响应式和异步式编程有时候是令人生畏的。
                    学习曲线中最陡峭的异步就是出错时如何分析和调试。</p>
            </div>
            <div class="paragraph">
                <p>在命令式世界，调试通常都是非常直观的：直接看 stack trace 就可以找到问题出现的位置，
                    以及：是否问题责任全部出在你自己的代码？问题是不是发生在某些库代码？如果是，
                    那你的哪部分代码调用了库，是不是传参不合适导致的问题？</p>
            </div>
            <div class="sect2">
                <h3 id="_典型的_reactor_stack_trace"><a class="anchor" href="#_典型的_reactor_stack_trace"></a>7.1. 典型的
                    Reactor Stack Trace</h3>
                <div class="paragraph">
                    <p>当你切换到异步代码，事情就变得复杂的多了。</p>
                </div>
                <div class="paragraph">
                    <p>看一下下边的 stack trace：</p>
                </div>
                <div class="listingblock">
                    <div class="title">一段典型的 Reactor stack trace</div>
                    <div class="content">
<pre class="CodeRay highlight"><code>java.lang.IndexOutOfBoundsException: Source emitted more than one item
	at reactor.core.publisher.MonoSingle$SingleSubscriber.onNext(MonoSingle.java:120)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.emitScalar(FluxFlatMap.java:380)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.onNext(FluxFlatMap.java:349)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:119)
	at reactor.core.publisher.FluxRange$RangeSubscription.slowPath(FluxRange.java:144)
	at reactor.core.publisher.FluxRange$RangeSubscription.request(FluxRange.java:99)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:172)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:316)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:94)
	at reactor.core.publisher.FluxRange.subscribe(FluxRange.java:68)
	at reactor.core.publisher.FluxMapFuseable.subscribe(FluxMapFuseable.java:67)
	at reactor.core.publisher.FluxFlatMap.subscribe(FluxFlatMap.java:98)
	at reactor.core.publisher.MonoSingle.subscribe(MonoSingle.java:58)
	at reactor.core.publisher.Mono.subscribeWith(Mono.java:2668)
	at reactor.core.publisher.Mono.subscribe(Mono.java:2629)
	at reactor.core.publisher.Mono.subscribe(Mono.java:2604)
	at reactor.core.publisher.Mono.subscribe(Mono.java:2582)
	at reactor.guide.GuideTests.debuggingCommonStacktrace(GuideTests.java:722)</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>这里边有好多信息，我们得到了一个 <code>IndexOutOfBoundsException</code>，内容是 "<strong>源发出了</strong>
                        <em>不止一个元素</em>"。</p>
                </div>
                <div class="paragraph">
                    <p>我们也许可以很快假定这个源是一个 Flux/Mono，并通过下一行提到的 <code>MonoSingle</code> 确定是 Mono。
                        看上去是来自一个 <code>single</code> 操作符的抱怨。</p>
                </div>
                <div class="paragraph">
                    <p>查看 Javadoc 中关于操作符 <code>Mono#single</code> 的说明，我们看到 <code>single</code> 有一个规定：
                        源必须只能发出一个元素。看来是有一个源发出了多于一个元素，从而违反了这一规定。</p>
                </div>
                <div class="paragraph">
                    <p>我们可以更进一步找出那个源吗？下边的这些内容帮不上什么忙，只是打印了一些内部的似乎是一个响应式链的信息，
                        主要是一些 <code>subscribe</code> 和 <code>request</code> 的调用。</p>
                </div>
                <div class="paragraph">
                    <p>粗略过一下这些行，我们至少可以勾画出一个大致的出问题的链：大概涉及一个 <code>MonoSingle</code>、一个
                        <code>FluxFlatMap</code>，以及一个 <code>FluxRange</code>（每一个都对应 trace 中的几行，但总体涉及这三个类）。
                        所以难道是 <code>range().flatMap().single()</code> 这样的链？</p>
                </div>
                <div class="paragraph">
                    <p>但是如果在我们的应用中多处都用到这一模式，那怎么办？通过这些还是不能确定什么，
                        搜索 <code>single</code> 也找不到问题所在。最后一行指向了我们的代码。我们似乎终于接近真相了。</p>
                </div>
                <div class="paragraph">
                    <p>不过，等等&#8230;&#8203; 当我们找到源码文件，我们只能找到一个已存在的 <code>Flux</code> 被订阅了，如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
                        <pre class="CodeRay highlight"><code data-lang="java">toDebug.subscribe(<span
                                class="predefined-type">System</span>.out::println, <span class="predefined-type">Throwable</span>::printStackTrace);</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>所有这些都发生在订阅时，但是 <code>Flux</code> 本身没有在这里 <em>声明</em> 。更糟的是，
                        当我们找到变量声明的地方，我们看到：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
                        <pre class="CodeRay highlight"><code data-lang="java"><span class="directive">public</span> Mono&lt;<span
                                class="predefined-type">String</span>&gt; toDebug; <span class="comment">//请忽略 public 的属性</span></code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>变量声明的地方并没有 <em>实例化</em> 。我们必须做最坏的打算，那就是在这个应用中，
                        可能在几个不同的代码路径上对这个变量赋了值，但我们不确定是哪一个导致了问题。</p>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                这是一种 Reactor 运行时错误，而不是编译错误。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>我们希望找到的是操作符在哪里添加到操作链上的 —— 也就是 <code>Flux</code> 在哪里
                        声明的。我们通常说这个 <code>Flux</code> 是被 <strong>组装（assembly）</strong> 的。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="debug-activate"><a class="anchor" href="#debug-activate"></a>7.2. 开启调试模式</h3>
                <div class="paragraph">
                    <p>即便 stack trace 能够对有些许经验的开发者传递一些信息，但是在一些复杂的情况下，
                        这并不是一种理想的方式。</p>
                </div>
                <div class="paragraph">
                    <p>幸运的是，Reactor 内置了一种面向调试的能力—— <strong>操作期测量（assembly-time instrumentation）</strong>。</p>
                </div>
                <div class="paragraph">
                    <p>这通过 <strong>在应用启动的时候</strong> （或至少在有问题的 <code>Flux</code> 或 <code>Mono</code> 实例化之前）
                        加入自定义的 <code>Hook.onOperator</code> 钩子（hook），如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
                        <pre class="CodeRay highlight"><code data-lang="java">Hooks.onOperatorDebug();</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>这行代码——通过包装操作符的构造方法，并在此捕捉 stack trace——来监测对这个
                        <code>Flux</code>（或 <code>Mono</code>）的操作符的调用（也就是“组装”链的地方）。由于这些在
                        操作链被声明的地方就搞定，这个 hook 应该在 <strong>早于</strong> 声明的时候被激活，
                        最保险的方式就是在你程序的最开始就激活它。</p>
                </div>
                <div class="paragraph">
                    <p>之后，如果发生了异常，导致失败的操作符能够找到捕捉点并补充 stack trace。</p>
                </div>
                <div class="paragraph">
                    <p>在下一小节，我们看一下 stack trace 会有什么不同，以及如何对其进行分析。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_阅读调试模式的_stack_trace"><a class="anchor" href="#_阅读调试模式的_stack_trace"></a>7.3. 阅读调试模式的 Stack
                    Trace</h3>
                <div class="paragraph">
                    <p>我们在对上边的例子激活 <code>operatorStacktrace</code> 调试功能后，stack trace 如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code>java.lang.IndexOutOfBoundsException: Source emitted more than one item
	at reactor.core.publisher.MonoSingle$SingleSubscriber.onNext(MonoSingle.java:120)
	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:314) <i class="conum"
                                                                                                     data-value="1"></i><b>(1)</b>
...
<i class="conum" data-value="2"></i><b>(2)</b>
...
	at reactor.core.publisher.Mono.subscribeWith(Mono.java:2668)
	at reactor.core.publisher.Mono.subscribe(Mono.java:2629)
	at reactor.core.publisher.Mono.subscribe(Mono.java:2604)
	at reactor.core.publisher.Mono.subscribe(Mono.java:2582)
	at reactor.guide.GuideTests.debuggingActivated(GuideTests.java:727)
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: <i class="conum"
                                                                              data-value="3"></i><b>(3)</b>
Assembly trace from producer [reactor.core.publisher.MonoSingle] : <i class="conum" data-value="4"></i><b>(4)</b>
	reactor.core.publisher.Flux.single(Flux.java:5335)
	reactor.guide.GuideTests.scatterAndGather(GuideTests.java:689)
	reactor.guide.GuideTests.populateDebug(GuideTests.java:702)
	org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
	org.junit.rules.RunRules.evaluate(RunRules.java:20)
Error has been observed by the following operator(s): <i class="conum" data-value="5"></i><b>(5)</b>
	|_	Flux.single(TestWatcher.java:55) <i class="conum" data-value="6"></i><b>(6)</b></code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>这一条是新的：可以发现外层操作符捕捉到了 stack trace。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>第一部分的 stack trace 多数与上边一样，显示了操作符内部的信息（所以省略了这一块）。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>从这里开始，是在调试模式下显示的内容。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>首先我们获得了关于操作符组装的信息。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="5"></i><b>5</b></td>
                            <td>以及错误沿着操作链传播的轨迹（从错误点到订阅点）。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="6"></i><b>6</b></td>
                            <td>每一个看到这个错误的操作符都会列出，包括类和行信息。如果操作符是在 Reactor
                                源码内部组装的，行信息会被忽略。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>可见，捕获的 stack trace 作为 <code>OnAssemblyException</code> 添加到原始错误信息的之后。有两部分，
                        但是第一部分更加有意思。它显示了操作符触发异常的路径。这里显示的是 <code>scatterAndGather</code>
                        方法中的 <code>single</code> 导致的问题，而 <code>scatterAndGather</code> 方法是在 JUnit 中被
                        <code>populateDebug</code>
                        方法调用的。</p>
                </div>
                <div class="paragraph">
                    <p>既然我们已经有足够的信息来查出罪魁祸首，我们就来看一下 <code>scatterAndGather</code> 方法吧：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="directive">private</span> Mono&lt;<span
        class="predefined-type">String</span>&gt; scatterAndGather(Flux&lt;<span class="predefined-type">String</span>&gt; urls) {
    <span class="keyword">return</span> urls.flatMap(url -&gt; doRequest(url))
           .single(); <i class="conum" data-value="1"></i><b>(1)</b>
}</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>找到了，就是这个 <code>single</code>。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>现在我们可以发现错误的根源是将多个 HTTP 请求转化为 URLs 的 <code>flatMap</code> 方法后边接的是 <code>single</code>，
                        这太严格了。使用 <code>git blame</code> 找到代码作者，并同他讨论过后，发现他是本来是想用不那么严格的 <code>take(1)</code>
                        方法的。</p>
                </div>
                <div class="paragraph">
                    <p><strong>我们解决了问题。</strong></p>
                </div>
                <div class="quoteblock">
                    <blockquote>
                        错误被以下这些操作符观察（observed）了：
                    </blockquote>
                </div>
                <div class="paragraph">
                    <p>调试信息的第二部分在这个例子中意义不大，因为错误实际发生在最后一个操作符上（离 <code>subscribe</code> 最近的一个）。
                        另一个例子可能更加清楚：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">FakeRepository.findAllUserByName(Flux.just(<span
        class="string"><span class="delimiter">&quot;</span><span class="content">pedro</span><span class="delimiter">&quot;</span></span>, <span
        class="string"><span class="delimiter">&quot;</span><span class="content">simon</span><span class="delimiter">&quot;</span></span>, <span
        class="string"><span class="delimiter">&quot;</span><span class="content">stephane</span><span
        class="delimiter">&quot;</span></span>))
              .transform(FakeUtils1.applyFilters)
              .transform(FakeUtils2.enrichUser)
              .blockLast();</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>现在想象一下在 <code>findAllUserByName</code> 内部有个 <code>map</code> 方法报错了。我们可能会看到如下的 trace：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="exception">Error</span> has been observed by the following operator(s):
        |_        Flux.map(FakeRepository.java:<span class="integer">27</span>)
        |_        Flux.map(FakeRepository.java:<span class="integer">28</span>)
        |_        Flux.filter(FakeUtils1.java:<span class="integer">29</span>)
        |_        Flux.transform(GuideDebuggingExtraTests.java:<span class="integer">41</span>)
        |_        Flux.elapsed(FakeUtils2.java:<span class="integer">30</span>)
        |_        Flux.transform(GuideDebuggingExtraTests.java:<span class="integer">42</span>)</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>这与链上收到错误通知的操作符是一致：</p>
                </div>
                <div class="olist arabic">
                    <ol class="arabic">
                        <li>
                            <p>异常源自第一个 <code>map</code>。</p>
                        </li>
                        <li>
                            <p>被第二个 <code>map</code> 看到（都在 <code>findAllUserByName</code> 方法中）。</p>
                        </li>
                        <li>
                            <p>接着被一个 <code>filter</code> 和一个 <code>transform</code> 看到，说明链的这部分是由一个可重复使用的转换方法组装的
                                （这里是 <code>applyFilters</code> 工具方法）。</p>
                        </li>
                        <li>
                            <p>最后被一个 <code>elapsed</code> 和一个 <code>transform</code> 看到，类似的， <code>elapsed</code>
                                由第二个转换方法（<code>enrichUser</code>）
                                组装。</p>
                        </li>
                    </ol>
                </div>
                <div class="paragraph">
                    <p>用这种形式的检测方式构造 stack trace 是成本较高的。也因此这种调试模式作为最终大招，
                        只应该在可控的方式下激活。</p>
                </div>
                <div class="sect3">
                    <h4 id="_用_code_checkpoint_code_方式替代"><a class="anchor" href="#_用_code_checkpoint_code_方式替代"></a>7.3.1.
                        用 <code>checkpoint()</code> 方式替代</h4>
                    <div class="paragraph">
                        <p>调试模式是全局性的，会影响到程序中每一个组装到一个 <code>Flux</code> 或 <code>Mono</code> 的操作符。好处在于可以进行
                            <strong>事后调试（after-the-fact debugging）</strong>：无论错误是什么，我们都会得到足够的调试信息。</p>
                    </div>
                    <div class="paragraph">
                        <p>就像前边见到的那样，这种全局性的调试会因为成本较高而影响性能（其影响在于生成的 stack traces 数量）。
                            如果我们能大概定位到疑似出问题的操作符的话就可以不用花那么大的成本。然而，问题出现后，
                            我们通常无法定位到哪一个操作符可能存在问题，因为缺少一些 trace 信息，我们得修改代码，
                            打开调试模式，期望能够复现问题。</p>
                    </div>
                    <div class="paragraph">
                        <p>这种情况下，我们需要切换到调试模式，并进行一些必要的准备工作以便能够更好的发现复现的问题，
                            并捕捉到所有的信息。（译者加：这两段感觉有点废话。。。）</p>
                    </div>
                    <div class="paragraph">
                        <p>如果你能确定是在你的代码中组装的响应式链存在问题，而且程序的可服务性又是很重要的，
                            那么你可以 <strong>使用 <code>checkpoint()</code> 操作符，它有两种调试技术可用</strong>。</p>
                    </div>
                    <div class="paragraph">
                        <p>你可以把这个操作符加到链中。这时 <code>checkpoint</code> 操作符就像是一个 hook，但只对它所在的链起作用。</p>
                    </div>
                    <div class="paragraph">
                        <p>还有一个 <code>checkpoint(String)</code> 的方法变体，你可以传入一个独特的字符串以方便在 assembly traceback 中识别信息。
                            这样会省略 stack trace，你可以依赖这个字符串（以下改称“定位描述符”）来定位到组装点。<code>checkpoint(String)</code> 比 <code>checkpoint</code>
                            有更低的执行成本。</p>
                    </div>
                    <div class="paragraph">
                        <p><code>checkpoint(String)</code> 在它的输出中包含 "light" （可以方便用于搜索），如下所示：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>...
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Assembly site of producer [reactor.core.publisher.FluxElapsed] is identified by light checkpoint [light checkpoint identifier].</pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>最后的但同样重要的是，如果你既想通过 checkpoint 添加定位描述符，同时又依赖于 stack trace
                            来定位组装点，你可以使用 <code>checkpoint("description", true)</code> 来实现这一点。这时回溯信息又出来了，
                            同时附加了定位描述符，如下例所示：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Assembly trace from producer [reactor.core.publisher.ParallelSource], described as [descriptionCorrelation1234] : <i
            class="conum" data-value="1"></i><b>(1)</b>
	reactor.core.publisher.ParallelFlux.checkpoint(ParallelFlux.java:174)
	reactor.core.publisher.FluxOnAssemblyTest.parallelFluxCheckpointDescription(FluxOnAssemblyTest.java:159)
Error has been observed by the following operator(s):
	|_	ParallelFlux.checkpointnull</pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td><code>descriptionCorrelation1234</code> 是通过 <code>checkpoint</code> 给出的定位描述符。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>定位描述符可以是静态的字符串、或人类可读的描述、或一个 <strong>correlation ID</strong>（例如，
                            来自 HTTP 请求头的信息）。</p>
                    </div>
                    <div class="admonitionblock note">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-note" title="Note"></i>
                                </td>
                                <td class="content">
                                    当全局调试模式和 <code>checkpoint()</code> 都开启的时候，checkpoint 的 stacks 输出会作为
                                    suppressed 错误输出，按照声明顺序添加在操作符图（graph）的后面。
                                </td>
                            </tr>
                        </table>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_记录流的日志"><a class="anchor" href="#_记录流的日志"></a>7.4. 记录流的日志</h3>
                <div class="paragraph">
                    <p>除了基于 stack trace 的调试和分析，还有一个有效的工具可以跟踪异步序列并记录日志。</p>
                </div>
                <div class="paragraph">
                    <p>就是 <code>log()</code> 操作符。将其加到操作链上之后，它会读（只读，peek）每一个
                        在其上游的 <code>Flux</code> 或 <code>Mono</code> 事件（包括 <code>onNext</code>、<code>onError</code>、
                        <code>onComplete</code>，
                        以及 <em>订阅</em>、 <em>取消</em>、和 <em>请求</em>）。</p>
                </div>
                <div class="sidebarblock">
                    <div class="content">
                        <div class="title">边注：关于 logging 的具体实现</div>
                        <div class="paragraph">
                            <p><code>log</code> 操作符通过 <strong><code>SLF4J</code></strong> 使用类似 Log4J 和 Logback
                                这样的公共的日志工具，
                                如果 SLF4J 不存在的话，则直接将日志输出到控制台。</p>
                        </div>
                        <div class="paragraph">
                            <p>控制台使用 <code>System.err</code> 记录 <code>WARN</code> 和 <code>ERROR</code> 级别的日志，使用
                                <code>System.out</code> 记录其他级别的日志。</p>
                        </div>
                        <div class="paragraph">
                            <p>如果你喜欢使用 JDK <code>java.util.logging</code>，在 3.0.x 你可以设置 JDK 的系统属性 <code>reactor.logging.fallback</code>。
                            </p>
                        </div>
                    </div>
                </div>
                <div class="paragraph">
                    <p>假设我们配置并激活了 <em>logback</em>，以及一个形如 <code>range(1,10).take(3)</code> 的操作链。通过将 <code>log()</code>
                        放在 <em>take</em> 之前，
                        我们就可以看到它内部是如何运行的，以及什么样的事件会向上游传播给 <em>range</em>，如下所示：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">Integer</span>&gt; flux = Flux.range(<span
        class="integer">1</span>, <span class="integer">10</span>)
                         .log()
                         .take(<span class="integer">3</span>);
flux.subscribe();</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>输出如下（通过 logger 的 console appender）：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre>10:45:20.200 [main] INFO  reactor.Flux.Range.1 - | onSubscribe([Synchronous Fuseable] FluxRange.RangeSubscription) <i
        class="conum" data-value="1"></i><b>(1)</b>
10:45:20.205 [main] INFO  reactor.Flux.Range.1 - | request(unbounded) <i class="conum" data-value="2"></i><b>(2)</b>
10:45:20.205 [main] INFO  reactor.Flux.Range.1 - | onNext(1) <i class="conum" data-value="3"></i><b>(3)</b>
10:45:20.205 [main] INFO  reactor.Flux.Range.1 - | onNext(2)
10:45:20.205 [main] INFO  reactor.Flux.Range.1 - | onNext(3)
10:45:20.205 [main] INFO  reactor.Flux.Range.1 - | cancel() <i class="conum" data-value="4"></i><b>(4)</b></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>这里，除了 logger 自己的格式（时间、线程、级别、消息），<code>log()</code> 操作符
                        还输出了其他一些格式化的东西：</p>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td><code>reactor.Flux.Range.1</code> 是自动生成的日志 <em>类别（category）</em>，以防你在操作链中多次使用
                                同一个操作符。通过它你可以分辨出来是哪个操作符的事件（这里是 <code>range</code> 的）。
                                你可以调用 <code>log(String)</code> 方法用自定义的类别替换这个标识符。在几个用于分隔的字符之后，
                                打印出了实际的事件。这里是一个 <code>onSubscribe</code> 调用、一个 <code>request</code> 调用、三个
                                <code>onNext</code> 调用，
                                以及一个 <code>cancel</code> 调用。对于第一行的 <code>onSubscribe</code>，我们知道了
                                <code>Subscriber</code> 的具体实现，
                                通常与操作符指定的实现是一致的，在方括号内有一些额外信息，包括这个操作符是否能够
                                通过同步或异步融合（fusion，具体见附录 <a href="#microfusion">[microfusion]</a>）的方式进行自动优化。
                            </td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>第二行，我们可以看到是一个由下游传播上来的个数无限的请求。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>然后 range 一下发出三个值。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>最后一行，我们看到了 <code>cancel()</code>。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>最后一行，<strong>(4)</strong>，最有意思。我们看到 <code>take</code> 在这里发挥作用了。在它拿到足够的元素之后，
                        就将序列切断了。简单来说，<code>take()</code> 导致源在发出用户请求的数量后 <code>cancel()</code> 了。</p>
                </div>
                <div class="paragraph">
                    <p><a class="fa fa-edit"
                          href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/debugging.adoc"
                          title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                        - "<a href="#debugging">调试 Reactor</a>"</p>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="advanced"><a class="anchor" href="#advanced"></a>8. 高级特性与概念</h2>
        <div class="sectionbody">
            <div class="paragraph">
                <p>这一章涉及如下的 Reactor 的高级特性与概念：</p>
            </div>
            <div class="ulist">
                <ul>
                    <li>
                        <p><a href="#advanced-mutualizing-operator-usage">打包重用操作符</a></p>
                    </li>
                    <li>
                        <p><a href="#reactor.hotCold">Hot vs Cold</a></p>
                    </li>
                    <li>
                        <p><a href="#advanced-broadcast-multiple-subscribers-connectableflux">使用
                            <code>ConnectableFlux</code> 对多个订阅者进行广播</a></p>
                    </li>
                    <li>
                        <p><a href="#advanced-three-sorts-batching">三种分批处理方式</a></p>
                    </li>
                    <li>
                        <p><a href="#advanced-parallelizing-parralelflux">使用 <code>ParallelFlux</code> 进行并行处理</a></p>
                    </li>
                    <li>
                        <p><a href="#scheduler-factory">替换默认的 <code>Schedulers</code></a></p>
                    </li>
                    <li>
                        <p><a href="#hooks">使用全局的 Hooks</a></p>
                    </li>
                    <li>
                        <p><a href="#context">增加一个 Context 到响应式序列</a></p>
                    </li>
                    <li>
                        <p><a href="#null-safety">空值安全</a></p>
                    </li>
                </ul>
            </div>
            <div class="sect2">
                <h3 id="advanced-mutualizing-operator-usage"><a class="anchor"
                                                                href="#advanced-mutualizing-operator-usage"></a>8.1.
                    打包重用操作符</h3>
                <div class="paragraph">
                    <p>从代码整洁的角度来说，重用代码是一个好办法。Reactor 提供了几种帮你打包重用代码的方式，
                        主要通过使用操作符或者常用的“操作符组合”的方法来实现。如果你觉得一段操作链很常用，
                        你可以将这段操作链打包封装后备用。</p>
                </div>
                <div class="sect3">
                    <h4 id="_使用_code_transform_code_操作符"><a class="anchor" href="#_使用_code_transform_code_操作符"></a>8.1.1.
                        使用 <code>transform</code> 操作符</h4>
                    <div class="paragraph">
                        <p><code>transform</code> 操作符可以将一段操作链封装为一个函数式（function）。这个函数式能在操作期（assembly time）
                            将被封装的操作链中的操作符还原并接入到调用 <code>transform</code> 的位置。这样做和直接将被封装的操作符
                            加入到链上的效果是一样的。示例如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Function&lt;Flux&lt;<span class="predefined-type">String</span>&gt;, Flux&lt;<span
        class="predefined-type">String</span>&gt;&gt; filterAndMap =
f -&gt; f.filter(color -&gt; !color.equals(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">orange</span><span class="delimiter">&quot;</span></span>))
      .map(<span class="predefined-type">String</span>::toUpperCase);

Flux.fromIterable(<span class="predefined-type">Arrays</span>.asList(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">blue</span><span class="delimiter">&quot;</span></span>, <span
            class="string"><span class="delimiter">&quot;</span><span class="content">green</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">orange</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">purple</span><span
            class="delimiter">&quot;</span></span>))
        .doOnNext(<span class="predefined-type">System</span>.out::println)
        .transform(filterAndMap)
        .subscribe(d -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Subscriber to Transformed MapAndFilter: </span><span
            class="delimiter">&quot;</span></span>+d));</code></pre>
                        </div>
                    </div>
                    <div class="imageblock">
                        <div class="content">
                            <img alt="Transform Operator : encapsulate flows"
                                 src="https://raw.githubusercontent.com/reactor/reactor-core/v3.0.7.RELEASE/src/docs/marble/gs-transform.png">
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>上边例子的输出如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>blue
Subscriber to Transformed MapAndFilter: BLUE
green
Subscriber to Transformed MapAndFilter: GREEN
orange
purple
Subscriber to Transformed MapAndFilter: PURPLE</pre>
                        </div>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_使用_code_compose_code_操作符"><a class="anchor" href="#_使用_code_compose_code_操作符"></a>8.1.2. 使用
                        <code>compose</code> 操作符</h4>
                    <div class="paragraph">
                        <p><code>compose</code> 操作符与 <code>transform</code> 类似，也能够将几个操作符封装到一个函数式中。
                            主要的区别就是，这个函数式作用到原始序列上的话，是 <strong>基于每一个订阅者的（on a per-subscriber
                                basis）</strong> 。这意味着它对每一个 subscription 可以生成不同的操作链（通过维护一些状态值）。
                            如下例所示：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="predefined-type">AtomicInteger</span> ai = <span
        class="keyword">new</span> <span class="predefined-type">AtomicInteger</span>();
Function&lt;Flux&lt;<span class="predefined-type">String</span>&gt;, Flux&lt;<span class="predefined-type">String</span>&gt;&gt; filterAndMap = f -&gt; {
        <span class="keyword">if</span> (ai.incrementAndGet() == <span class="integer">1</span>) {
<span class="keyword">return</span> f.filter(color -&gt; !color.equals(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">orange</span><span class="delimiter">&quot;</span></span>))
        .map(<span class="predefined-type">String</span>::toUpperCase);
        }
        <span class="keyword">return</span> f.filter(color -&gt; !color.equals(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">purple</span><span
            class="delimiter">&quot;</span></span>))
                .map(<span class="predefined-type">String</span>::toUpperCase);
};

Flux&lt;<span class="predefined-type">String</span>&gt; composedFlux =
Flux.fromIterable(<span class="predefined-type">Arrays</span>.asList(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">blue</span><span class="delimiter">&quot;</span></span>, <span
            class="string"><span class="delimiter">&quot;</span><span class="content">green</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">orange</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">purple</span><span
            class="delimiter">&quot;</span></span>))
    .doOnNext(<span class="predefined-type">System</span>.out::println)
    .compose(filterAndMap);

composedFlux.subscribe(d -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Subscriber 1 to Composed MapAndFilter :</span><span
            class="delimiter">&quot;</span></span>+d));
composedFlux.subscribe(d -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Subscriber 2 to Composed MapAndFilter: </span><span
            class="delimiter">&quot;</span></span>+d));</code></pre>
                        </div>
                    </div>
                    <div class="imageblock">
                        <div class="content">
                            <img alt="Compose Operator : Per Subscriber transformation"
                                 src="https://raw.githubusercontent.com/reactor/reactor-core/v3.0.7.RELEASE/src/docs/marble/gs-compose.png">
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>上边的例子输出如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre>blue
Subscriber 1 to Composed MapAndFilter :BLUE
green
Subscriber 1 to Composed MapAndFilter :GREEN
orange
purple
Subscriber 1 to Composed MapAndFilter :PURPLE
blue
Subscriber 2 to Composed MapAndFilter: BLUE
green
Subscriber 2 to Composed MapAndFilter: GREEN
orange
Subscriber 2 to Composed MapAndFilter: ORANGE
purple</pre>
                        </div>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="reactor.hotCold"><a class="anchor" href="#reactor.hotCold"></a>8.2. Hot vs Cold</h3>
                <div class="paragraph">
                    <p>到目前为止，我们一直认为 <code>Flux</code>（和 <code>Mono</code>）都是这样的：它们都代表了一种异步的数据序列，
                        在订阅（subscribe）之前什么都不会发生。</p>
                </div>
                <div class="paragraph">
                    <p>但是实际上，广义上有两种发布者：“热”与“冷”（<strong>hot</strong> and <strong>cold</strong>）。</p>
                </div>
                <div class="paragraph">
                    <p>（本文档）到目前介绍的其实都是 <strong>cold</strong> 家族的发布者。它们为每一个订阅（subscription）
                        都生成数据。如果没有创建任何订阅（subscription），那么就不会生成数据。</p>
                </div>
                <div class="paragraph">
                    <p>试想一个 HTTP 请求：每一个新的订阅者都会触发一个 HTTP 调用，但是如果没有订阅者关心结果的话，
                        那就不会有任何调用。</p>
                </div>
                <div class="paragraph">
                    <p>另一方面，<strong>热</strong> 发布者，不依赖于订阅者的数量。即使没有订阅者它们也会发出数据，
                        如果有一个订阅者接入进来，那么它就会收到订阅之后发出的元素。对于热发布者，
                        在你订阅它之前，确实已经发生了什么。</p>
                </div>
                <div class="paragraph">
                    <p><code>just</code> 是 Reactor 中少数几个“热”操作符的例子之一：它直接在组装期（assembly time）
                        就拿到数据，如果之后有谁订阅它，就重新发送数据给订阅者。再拿 HTTP 调用举例，如果给 <code>just</code>
                        传入的数据是一个 HTTP 调用的结果，那么之后在初始化 <em>just</em> 的时候才会进行唯一的一次网络调用。</p>
                </div>
                <div class="paragraph">
                    <p>如果想将 <code>just</code> 转化为一种 <em>冷</em> 的发布者，你可以使用 <code>defer</code>。它能够将刚才例子中对
                        HTTP 的请求延迟到订阅时（这样的话，对于每一个新的订阅来说，都会发生一次网络调用）。</p>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                Reactor 中多数其他的 <em>热</em> 发布者是扩展自 <code>Processor</code> 的。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>考虑其他两个例子，如下是第一个例子：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; source = Flux.fromIterable(<span
        class="predefined-type">Arrays</span>.asList(<span class="string"><span class="delimiter">&quot;</span><span
        class="content">blue</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
        class="delimiter">&quot;</span><span class="content">green</span><span
        class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
        class="content">orange</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
        class="delimiter">&quot;</span><span class="content">purple</span><span class="delimiter">&quot;</span></span>))
                          .doOnNext(<span class="predefined-type">System</span>.out::println)
                          .filter(s -&gt; s.startsWith(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">o</span><span class="delimiter">&quot;</span></span>))
                          .map(<span class="predefined-type">String</span>::toUpperCase);

source.subscribe(d -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Subscriber 1: </span><span
            class="delimiter">&quot;</span></span>+d));
source.subscribe(d -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Subscriber 2: </span><span
            class="delimiter">&quot;</span></span>+d));</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>第一个例子输出如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre>blue
green
orange
Subscriber 1: ORANGE
purple
blue
green
orange
Subscriber 2: ORANGE
purple</pre>
                    </div>
                </div>
                <div class="imageblock">
                    <div class="content">
                        <img alt="Replaying behavior"
                             src="https://raw.githubusercontent.com/reactor/reactor-core/v3.0.7.RELEASE/src/docs/marble/gs-cold.png">
                    </div>
                </div>
                <div class="paragraph">
                    <p>两个订阅者都触发了所有的颜色，因为每一个订阅者都会让构造 <code>Flux</code> 的操作符运行一次。</p>
                </div>
                <div class="paragraph">
                    <p>将下边的例子与第一个例子对比：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">UnicastProcessor&lt;<span class="predefined-type">String</span>&gt; hotSource = UnicastProcessor.create();

Flux&lt;<span class="predefined-type">String</span>&gt; hotFlux = hotSource.publish()
                                .autoConnect()
                                .map(<span class="predefined-type">String</span>::toUpperCase);


hotFlux.subscribe(d -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Subscriber 1 to Hot Source: </span><span
            class="delimiter">&quot;</span></span>+d));

hotSource.onNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">blue</span><span
            class="delimiter">&quot;</span></span>);
hotSource.onNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">green</span><span
            class="delimiter">&quot;</span></span>);

hotFlux.subscribe(d -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Subscriber 2 to Hot Source: </span><span
            class="delimiter">&quot;</span></span>+d));

hotSource.onNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">orange</span><span
            class="delimiter">&quot;</span></span>);
hotSource.onNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">purple</span><span
            class="delimiter">&quot;</span></span>);
hotSource.onComplete();</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>第二个例子输出如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre>Subscriber 1 to Hot Source: BLUE
Subscriber 1 to Hot Source: GREEN
Subscriber 1 to Hot Source: ORANGE
Subscriber 2 to Hot Source: ORANGE
Subscriber 1 to Hot Source: PURPLE
Subscriber 2 to Hot Source: PURPLE</pre>
                    </div>
                </div>
                <div class="imageblock">
                    <div class="content">
                        <img alt="Broadcasting a subscription"
                             src="https://raw.githubusercontent.com/reactor/reactor-core/v3.0.7.RELEASE/src/docs/marble/gs-hot.png">
                    </div>
                </div>
                <div class="paragraph">
                    <p>第一个订阅者收到了所有的四个颜色，第二个订阅者由于是在前两个颜色发出之后订阅的，
                        故而收到了之后的两个颜色，在输出中有两次 "ORANGE" 和 "PURPLE"。从这个例子可见，
                        无论是否有订阅者接入进来，这个 Flux 都会运行。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="advanced-broadcast-multiple-subscribers-connectableflux"><a class="anchor"
                                                                                    href="#advanced-broadcast-multiple-subscribers-connectableflux"></a>8.3.
                    使用 <code>ConnectableFlux</code> 对多个订阅者进行广播</h3>
                <div class="paragraph">
                    <p>有时候，你不仅想要延迟到某一个订阅者订阅之后才开始发出数据，可能还希望在多个订阅者
                        <em>到齐</em> <strong>之后</strong> 才开始。</p>
                </div>
                <div class="paragraph">
                    <p><code>ConnectableFlux</code> 的用意便在于此。<code>Flux</code> API 中有两种主要的返回 <code>ConnectableFlux</code>
                        的方式：<code>publish</code> 和 <code>replay</code>。</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><code>publish</code> 会尝试满足各个不同订阅者的需求（背压），并综合这些请求反馈给源。
                                尤其是如果有某个订阅者的需求为 <code>0</code>，publish 会 <strong>暂停</strong> 它对源的请求。</p>
                        </li>
                        <li>
                            <p><code>replay</code> 将对第一个订阅后产生的数据进行缓存，最多缓存数量取决于配置（时间/缓存大小）。
                                它会对后续接入的订阅者重新发送数据。</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p><code>ConnectableFlux</code> 提供了多种对下游订阅的管理。包括：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><code>connect</code> 当有足够的订阅接入后，可以对 flux 手动执行一次。它会触发对上游源的订阅。</p>
                        </li>
                        <li>
                            <p><code>autoConnect(n)</code> 与 connect 类似，不过是在有 <code>n</code> 个订阅的时候自动触发。</p>
                        </li>
                        <li>
                            <p><code>refCount(n)</code> 不仅能够在订阅者接入的时候自动触发，还会检测订阅者的取消动作。如果订阅者数量不够，
                                会将源“断开连接”，再有新的订阅者接入的时候才会继续“连上”源。</p>
                        </li>
                        <li>
                            <p><code>refCount(int, Duration)</code> 增加了一个 "优雅的倒计时"：一旦订阅者数量太低了，它会等待
                                <code>Duration</code> 的时间，如果没有新的订阅者接入才会与源“断开连接”。</p>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p>示例如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">Integer</span>&gt; source = Flux.range(<span
        class="integer">1</span>, <span class="integer">3</span>)
                           .doOnSubscribe(s -&gt; <span class="predefined-type">System</span>.out.println(<span
            class="string"><span class="delimiter">&quot;</span><span class="content">subscribed to source</span><span
            class="delimiter">&quot;</span></span>));

ConnectableFlux&lt;<span class="predefined-type">Integer</span>&gt; co = source.publish();

co.subscribe(<span class="predefined-type">System</span>.out::println, e -&gt; {}, () -&gt; {});
co.subscribe(<span class="predefined-type">System</span>.out::println, e -&gt; {}, () -&gt; {});

<span class="predefined-type">System</span>.out.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">done subscribing</span><span class="delimiter">&quot;</span></span>);
<span class="predefined-type">Thread</span>.sleep(<span class="integer">500</span>);
<span class="predefined-type">System</span>.out.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">will now connect</span><span class="delimiter">&quot;</span></span>);

co.connect();</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>The preceding code produces the following output:</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre>done subscribing
will now connect
subscribed to source
1
1
2
2
3
3</pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>使用 <code>autoConnect</code>：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">Integer</span>&gt; source = Flux.range(<span
        class="integer">1</span>, <span class="integer">3</span>)
                           .doOnSubscribe(s -&gt; <span class="predefined-type">System</span>.out.println(<span
            class="string"><span class="delimiter">&quot;</span><span class="content">subscribed to source</span><span
            class="delimiter">&quot;</span></span>));

Flux&lt;<span class="predefined-type">Integer</span>&gt; autoCo = source.publish().autoConnect(<span
            class="integer">2</span>);

autoCo.subscribe(<span class="predefined-type">System</span>.out::println, e -&gt; {}, () -&gt; {});
<span class="predefined-type">System</span>.out.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">subscribed first</span><span class="delimiter">&quot;</span></span>);
<span class="predefined-type">Thread</span>.sleep(<span class="integer">500</span>);
<span class="predefined-type">System</span>.out.println(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">subscribing second</span><span class="delimiter">&quot;</span></span>);
autoCo.subscribe(<span class="predefined-type">System</span>.out::println, e -&gt; {}, () -&gt; {});</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>以上代码输出如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre>subscribed first
subscribing second
subscribed to source
1
1
2
2
3
3</pre>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="advanced-three-sorts-batching"><a class="anchor" href="#advanced-three-sorts-batching"></a>8.4.
                    三种分批处理方式</h3>
                <div class="paragraph">
                    <p>当你有许多的元素，并且想将他们分批处理，Reactor 总体上有三种方案：分组（grouping）、
                        窗口（windowing）（译者注：感觉这个不翻译更明白。。。）、缓存（buffering）。
                        这三种在概念上类似，因为它们都是将 <code>Flux&lt;T&gt;</code> 进行聚集。分组和分段操作都会创建一个
                        <code>Flux&lt;Flux&lt;T&gt;&gt;</code>，而缓存操作得到的是一个 <code>Collection&lt;T&gt;</code>（译者注：应该是一个
                        <code>Flux&lt;Collection&lt;T&gt;&gt;</code>)。</p>
                </div>
                <div class="sect3">
                    <h4 id="_用_code_flux_groupedflux_t_code_进行分组"><a class="anchor"
                                                                     href="#_用_code_flux_groupedflux_t_code_进行分组"></a>8.4.1.
                        用 <code>Flux&lt;GroupedFlux&lt;T&gt;&gt;</code> 进行分组</h4>
                    <div class="paragraph">
                        <p>分组能够根据 <strong>key</strong> 将源 <code>Flux&lt;T&gt;</code> 拆分为多个批次。</p>
                    </div>
                    <div class="paragraph">
                        <p>对应的操作符是 <code>groupBy</code>。</p>
                    </div>
                    <div class="paragraph">
                        <p>每一组用 <code>GroupedFlux&lt;T&gt;</code> 类型表示，使用它的 <code>key()</code> 方法可以得到该组的 key。</p>
                    </div>
                    <div class="paragraph">
                        <p>在组内，元素并不需要是连续的。当源发出一个新的元素，该元素会被分发到与之匹配的 key
                            所对应的组中（如果还没有该 key 对应的组，则创建一个）。</p>
                    </div>
                    <div class="paragraph">
                        <p>这意味着组：</p>
                    </div>
                    <div class="olist arabic">
                        <ol class="arabic">
                            <li>
                                <p>是互相没有交集的（一个元素只属于一个组）。</p>
                            </li>
                            <li>
                                <p>会包含原始序列中任意位置的元素。</p>
                            </li>
                            <li>
                                <p>不会为空。</p>
                            </li>
                        </ol>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">StepVerifier.create(
        Flux.just(<span class="integer">1</span>, <span class="integer">3</span>, <span class="integer">5</span>, <span
            class="integer">2</span>, <span class="integer">4</span>, <span class="integer">6</span>, <span
            class="integer">11</span>, <span class="integer">12</span>, <span class="integer">13</span>)
                .groupBy(i -&gt; i % <span class="integer">2</span> == <span class="integer">0</span> ? <span
            class="string"><span class="delimiter">&quot;</span><span class="content">even</span><span
            class="delimiter">&quot;</span></span> : <span class="string"><span class="delimiter">&quot;</span><span
            class="content">odd</span><span class="delimiter">&quot;</span></span>)
                .concatMap(g -&gt; g.defaultIfEmpty(-<span class="integer">1</span>) <span class="comment">//如果组为空，显示为 -1</span>
                                .map(<span class="predefined-type">String</span>::valueOf) <span class="comment">//转换为字符串</span>
                                .startWith(g.key())) <span class="comment">//以该组的 key 开头</span>
        )
        .expectNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">odd</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">1</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">3</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">5</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">11</span><span class="delimiter">&quot;</span></span>, <span
            class="string"><span class="delimiter">&quot;</span><span class="content">13</span><span class="delimiter">&quot;</span></span>)
        .expectNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">even</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">2</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">4</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">6</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">12</span><span class="delimiter">&quot;</span></span>)
        .verifyComplete();</code></pre>
                        </div>
                    </div>
                    <div class="admonitionblock warning">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-warning" title="Warning"></i>
                                </td>
                                <td class="content">
                                    分组操作适用于分组个数不多的场景。而且所有的组都必须被消费，这样 <code>groupBy</code>
                                    才能持续从上游获取数据。有时候这两种要求在一起——比如元素数量超多，
                                    但是并行的用来消费的 <code>flatMap</code> 又太少的时候——会导致程序卡死。
                                </td>
                            </tr>
                        </table>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_使用_code_flux_flux_t_code_进行_window_操作"><a class="anchor"
                                                                       href="#_使用_code_flux_flux_t_code_进行_window_操作"></a>8.4.2.
                        使用 <code>Flux&lt;Flux&lt;T&gt;&gt;</code> 进行 window 操作</h4>
                    <div class="paragraph">
                        <p>window 操作是 根据个数、时间等条件，或能够定义边界的发布者（boundary-defining <code>Publisher</code>），
                            把源 <code>Flux&lt;T&gt;</code> 拆分为 <em>windows</em>。</p>
                    </div>
                    <div class="paragraph">
                        <p>对应的操作符有 <code>window</code>、<code>windowTimeout</code>、<code>windowUntil</code>、<code>windowWhile</code>，以及
                            <code>windowWhen</code>。</p>
                    </div>
                    <div class="paragraph">
                        <p>与 <code>groupBy</code> 的主要区别在于，窗口操作能够保持序列顺序。并且同一时刻最多只能有两个 window
                            是开启的。</p>
                    </div>
                    <div class="paragraph">
                        <p>它们 <strong>可以</strong> 重叠。操作符参数有 <code>maxSize</code> 和
                            <code>skip</code>，<code>maxSize</code> 指定收集多少个元素就关闭
                            window，而 <code>skip</code> 指定收集多数个元素后就打开下一个 window。所以如果 <code>maxSize &gt; skip</code> 的话，
                            一个新的 window 的开启会先于当前 window 的关闭， 从而二者会有重叠。</p>
                    </div>
                    <div class="paragraph">
                        <p>重叠的 window 示例如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">StepVerifier.create(
        Flux.range(<span class="integer">1</span>, <span class="integer">10</span>)
                .window(<span class="integer">5</span>, <span class="integer">3</span>) <span class="comment">//overlapping windows</span>
                .concatMap(g -&gt; g.defaultIfEmpty(-<span class="integer">1</span>)) <span class="comment">//将 windows 显示为 -1</span>
        )
                .expectNext(<span class="integer">1</span>, <span class="integer">2</span>, <span
            class="integer">3</span>, <span class="integer">4</span>, <span class="integer">5</span>)
                .expectNext(<span class="integer">4</span>, <span class="integer">5</span>, <span
            class="integer">6</span>, <span class="integer">7</span>, <span class="integer">8</span>)
                .expectNext(<span class="integer">7</span>, <span class="integer">8</span>, <span
            class="integer">9</span>, <span class="integer">10</span>)
                .expectNext(<span class="integer">10</span>)
                .verifyComplete();</code></pre>
                        </div>
                    </div>
                    <div class="admonitionblock note">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-note" title="Note"></i>
                                </td>
                                <td class="content">
                                    如果将两个参数的配置反过来（<code>maxSize</code> &lt; <code>skip</code>），序列中的一些元素就会被丢弃掉，
                                    而不属于任何 window。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>对基于判断条件的 <code>windowUntil</code> 和 <code>windowWhile</code>，如果序列中的元素不匹配判断条件，
                            那么可能导致 <em>空 windows</em>，如下例所示：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">StepVerifier.create(
        Flux.just(<span class="integer">1</span>, <span class="integer">3</span>, <span class="integer">5</span>, <span
            class="integer">2</span>, <span class="integer">4</span>, <span class="integer">6</span>, <span
            class="integer">11</span>, <span class="integer">12</span>, <span class="integer">13</span>)
                .windowWhile(i -&gt; i % <span class="integer">2</span> == <span class="integer">0</span>)
                .concatMap(g -&gt; g.defaultIfEmpty(-<span class="integer">1</span>))
        )
                .expectNext(-<span class="integer">1</span>, -<span class="integer">1</span>, -<span
            class="integer">1</span>) <span class="comment">//分别被奇数 1 3 5 触发</span>
                .expectNext(<span class="integer">2</span>, <span class="integer">4</span>, <span
            class="integer">6</span>) <span class="comment">// 被 11 触发</span>
                .expectNext(<span class="integer">12</span>) <span class="comment">// 被 13 触发</span>
                .expectNext(-<span class="integer">1</span>) <span class="comment">// 空的 completion window，如果 onComplete 前的元素能够匹配上的话就没有这个了</span>
                .verifyComplete();</code></pre>
                        </div>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_使用_code_flux_list_t_code_进行缓存"><a class="anchor"
                                                               href="#_使用_code_flux_list_t_code_进行缓存"></a>8.4.3. 使用
                        <code>Flux&lt;List&lt;T&gt;&gt;</code> 进行缓存</h4>
                    <div class="paragraph">
                        <p>缓存与窗口类似，不同在于：缓存操作之后会发出 <em>buffers</em> （类型为`Collection&lt;T&gt;<code>，
                            默认是 `List&lt;T&gt;</code>)，而不是 <em>windows</em> （类型为 <code>Flux&lt;T&gt;</code>）。</p>
                    </div>
                    <div class="paragraph">
                        <p>
                            缓存的操作符与窗口的操作符是对应的：<code>buffer</code>、<code>bufferTimeout</code>、<code>bufferUntil</code>、<code>bufferWhile</code>，
                            以及`bufferWhen`。</p>
                    </div>
                    <div class="paragraph">
                        <p>如果说对于窗口操作符来说，是开启一个窗口，那么对于缓存操作符来说，就是创建一个新的集合，
                            然后对其添加元素。而窗口操作符在关闭窗口的时候，缓存操作符则是发出一个集合。</p>
                    </div>
                    <div class="paragraph">
                        <p>缓存操作也会有丢弃元素或内容重叠的情况，如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">StepVerifier.create(
        Flux.range(<span class="integer">1</span>, <span class="integer">10</span>)
                .buffer(<span class="integer">5</span>, <span class="integer">3</span>) <span
            class="comment">// 缓存重叠</span>
        )
                .expectNext(<span class="predefined-type">Arrays</span>.asList(<span class="integer">1</span>, <span
            class="integer">2</span>, <span class="integer">3</span>, <span class="integer">4</span>, <span
            class="integer">5</span>))
                .expectNext(<span class="predefined-type">Arrays</span>.asList(<span class="integer">4</span>, <span
            class="integer">5</span>, <span class="integer">6</span>, <span class="integer">7</span>, <span
            class="integer">8</span>))
                .expectNext(<span class="predefined-type">Arrays</span>.asList(<span class="integer">7</span>, <span
            class="integer">8</span>, <span class="integer">9</span>, <span class="integer">10</span>))
                .expectNext(<span class="predefined-type">Collections</span>.singletonList(<span
            class="integer">10</span>))
                .verifyComplete();</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>不像窗口方法，<code>bufferUntil</code> 和 <code>bufferWhile</code> 不会发出空的 buffer，如下例所示：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">StepVerifier.create(
        Flux.just(<span class="integer">1</span>, <span class="integer">3</span>, <span class="integer">5</span>, <span
            class="integer">2</span>, <span class="integer">4</span>, <span class="integer">6</span>, <span
            class="integer">11</span>, <span class="integer">12</span>, <span class="integer">13</span>)
                .bufferWhile(i -&gt; i % <span class="integer">2</span> == <span class="integer">0</span>)
        )
        .expectNext(<span class="predefined-type">Arrays</span>.asList(<span class="integer">2</span>, <span
            class="integer">4</span>, <span class="integer">6</span>)) <span class="comment">// 被 11 触发</span>
        .expectNext(<span class="predefined-type">Collections</span>.singletonList(<span
            class="integer">12</span>)) <span class="comment">// 被 13 触发</span>
        .verifyComplete();</code></pre>
                        </div>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="advanced-parallelizing-parralelflux"><a class="anchor"
                                                                href="#advanced-parallelizing-parralelflux"></a>8.5. 使用
                    <code>ParallelFlux</code> 进行并行处理</h3>
                <div class="paragraph">
                    <p>如今多核架构已然普及，能够方便的进行并行处理是很重要的。Reactor 提供了一种特殊的类型
                        <code>ParallelFlux</code> 来实现并行，它能够将操作符调整为并行处理方式。</p>
                </div>
                <div class="paragraph">
                    <p>你可以对任何 <code>Flux</code> 使用 <code>parallel()</code> 操作符来得到一个 <code>ParallelFlux</code>.
                        <strong>不过这个操作符本身并不会进行并行处理</strong>，而是将负载划分到多个“轨道（rails）”上
                        （默认情况下，轨道个数与 CPU 核数相等）。</p>
                </div>
                <div class="paragraph">
                    <p>为了配置 ParallelFlux 如何并行地执行每一个轨道，你需要使用 <code>runOn(Scheduler)</code>。
                        注意，<code>Schedulers.parallel()</code> 是推荐的专门用于并行处理的调度器。</p>
                </div>
                <div class="paragraph">
                    <p>下边有两个用于比较的例子，第一个如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.range(<span class="integer">1</span>, <span class="integer">10</span>)
    .parallel(<span class="integer">2</span>) <i class="conum" data-value="1"></i><b>(1)</b>
    .subscribe(i -&gt; <span class="predefined-type">System</span>.out.println(<span
            class="predefined-type">Thread</span>.currentThread().getName() + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> -&gt; </span><span
            class="delimiter">&quot;</span></span> + i));</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>我们给定一个轨道数字，而不是依赖于 CPU 核数。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>下边是第二个例子：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux.range(<span class="integer">1</span>, <span class="integer">10</span>)
    .parallel(<span class="integer">2</span>)
    .runOn(Schedulers.parallel())
    .subscribe(i -&gt; <span class="predefined-type">System</span>.out.println(<span
            class="predefined-type">Thread</span>.currentThread().getName() + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> -&gt; </span><span
            class="delimiter">&quot;</span></span> + i));</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>第一个例子输出如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre>main -&gt; 1
main -&gt; 2
main -&gt; 3
main -&gt; 4
main -&gt; 5
main -&gt; 6
main -&gt; 7
main -&gt; 8
main -&gt; 9
main -&gt; 10</pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>第二个例子在两个线程中并行执行，输出如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre>parallel-1 -&gt; 1
parallel-2 -&gt; 2
parallel-1 -&gt; 3
parallel-2 -&gt; 4
parallel-1 -&gt; 5
parallel-2 -&gt; 6
parallel-1 -&gt; 7
parallel-1 -&gt; 9
parallel-2 -&gt; 8
parallel-2 -&gt; 10</pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>如果在并行地处理之后，需要退回到一个“正常”的 <code>Flux</code> 而使后续的操作链按非并行模式执行，
                        你可以对 <code>ParallelFlux</code> 使用 <code>sequential()</code> 方法。</p>
                </div>
                <div class="paragraph">
                    <p>注意，当你在对 ParallelFlux 使用一个 <code>Subscriber</code> 而不是基于 lambda 进行订阅（<code>subscribe()</code>）
                        的时候，<code>sequential()</code> 会自动地被偷偷应用。</p>
                </div>
                <div class="paragraph">
                    <p>注意 <code>subscribe(Subscriber&lt;T&gt;)</code> 会合并所有的执行轨道，而
                        <code>subscribe(Consumer&lt;T&gt;)</code> 会在所有轨道上运行。
                        如果 <code>subscribe()</code> 方法中是一个 lambda，那么有几个轨道，lambda 就会被执行几次。</p>
                </div>
                <div class="paragraph">
                    <p>你还可以使用 <code>groups()</code> 作为 <code>Flux&lt;GroupedFlux&lt;T&gt;&gt;</code> 进入到各个轨道或组里边，
                        然后可以通过 <code>composeGroup()</code> 添加额外的操作符。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="scheduler-factory"><a class="anchor" href="#scheduler-factory"></a>8.6. 替换默认的
                    <code>Schedulers</code></h3>
                <div class="paragraph">
                    <p>就像我们在 <a href="#schedulers">调度器（Schedulers）</a> 这一节看到的那样， Reactor Core 内置许多
                        <code>Scheduler</code> 的具体实现。
                        你可以用形如 <code>new*</code> 的工厂方法来创建调度器，每一种调度器都有一个单例对象，你可以使用单例工厂方法
                        （比如 <code>Schedulers.elastic()</code> 而不是 <code>Schedulers.newElastic()</code>）来获取它。</p>
                </div>
                <div class="paragraph">
                    <p>当你不明确指定调度器的时候，那些需要调度器的操作符会使用这些默认的单例调度器对象。例如，
                        <code>Flux#delayElements(Duration)</code> 使用的是 <code>Schedulers.parallel()</code> 调度器对象。</p>
                </div>
                <div class="paragraph">
                    <p>然而有些情况下，你可能需要“一刀切”（就不用对每一个操作符都传入你自己的调度器作为参数了）
                        地调整这些默认调度器。 一个典型的例子就是，假设你需要对每一个被调度的任务统计执行时长，
                        就想把默认的调度器包装一下，然后添加计时功能。</p>
                </div>
                <div class="paragraph">
                    <p>那么可以使用 <code>Schedulers.Factory</code> 类来改变默认的调度器。默认情况下，一个 <code>Factory</code> 会使用一些“命名比较直白”
                        的方法来创建所有的标准 <code>Scheduler</code>。每一个方法你都可以用自己的实现方式来重写。</p>
                </div>
                <div class="paragraph">
                    <p>此外，<code>Factory</code> 还提供一个额外的自定义方法 <code>decorateExecutorService</code>。它会在创建每一个
                        reactor-core 调度器——内部有一个 <code>ScheduledExecutorService</code>（即使是比如用
                        <code>Schedulers.newParallel()</code> 方法创建的这种非默认的调度器）——的时候被调用。</p>
                </div>
                <div class="paragraph">
                    <p>你可以通过调整 <code>ScheduledExecutorService</code> 来改变调度器：（译者加：<code>decorateExecutorService</code>
                        方法）通过一个 <code>Supplier</code> 参数暴露出来，你可以直接绕过这个 supplier 返回你自己的调度器实例，或者用
                        （译者加： <code>Schedulers.ScheduledExecutorService</code> 的）<code>get()</code> 得到默认实例，然后包装它，
                        这取决于配置的调度器类型。</p>
                </div>
                <div class="admonitionblock important">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-important" title="Important"></i>
                            </td>
                            <td class="content">
                                当你搞定了一个定制好的 <code>Factory</code> 后，你必须使用 <code>Schedulers.setFactory(Factory)</code>
                                方法来安装它。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>最后，对于调度器来说，有一个可自定义的 hook：<code>onHandleError</code>。这个 hook 会在提交到这个调度器的
                        <code>Runnable</code> 任务抛出异常的时候被调用（注意，如果还设置了一个 <code>UncaughtExceptionHandler</code>，
                        那么它和 hook 都会被调用）。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="hooks"><a class="anchor" href="#hooks"></a>8.7. 使用全局的 Hooks</h3>
                <div class="paragraph">
                    <p>Reactor 还有另外一类可配置的应用于多种场合的回调，它们都在 <code>Hooks</code> 类中定义，总体来说有三类：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p><a href="#hooks-dropping">丢弃事件的 Hooks</a></p>
                        </li>
                        <li>
                            <p><a href="#hooks-internal">内部错误 Hook</a></p>
                        </li>
                        <li>
                            <p><a href="#hooks-assembly">组装 Hooks</a></p>
                        </li>
                    </ul>
                </div>
                <div class="sect3">
                    <h4 id="hooks-dropping"><a class="anchor" href="#hooks-dropping"></a>8.7.1. 丢弃事件的 Hooks</h4>
                    <div class="paragraph">
                        <p>当生成源的操作符不遵从响应式流规范的时候，Dropping hooks（用于处理丢弃事件的 hooks）会被调用。
                            这种类型的错误是处于正常的执行路径之外的（也就是说它们不能通过 <code>onError</code> 传播）。</p>
                    </div>
                    <div class="paragraph">
                        <p>典型的例子是，假设一个发布者即使在被调用 <code>onCompleted</code> 之后仍然可以通过操作符调用 <code>onNext</code>。
                            这种情况下，<code>onNext</code> 的值会被 <em>丢弃</em>，如果有多余的 <code>onError</code> 的信号亦是如此。</p>
                    </div>
                    <div class="paragraph">
                        <p>相应的 hook，<code>onNextDropped</code> 以及 <code>onErrorDropped</code>，可以提供一个全局的
                            <code>Consumer</code>，
                            以便能够在丢弃的情况发生时进行处理。例如，你可以使用它来对丢弃事件记录日志，或进行资源清理
                            （使用资源的值可能压根没有到达响应式链的下游）。</p>
                    </div>
                    <div class="paragraph">
                        <p>连续设置两次 hook 的话都会起作用：提供的每一个 consumer 都会被调用。使用 <code>Hooks.resetOn*Dropped()</code>
                            方法可以将 hooks 全部重置为默认。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="hooks-internal"><a class="anchor" href="#hooks-internal"></a>8.7.2. 内部错误 Hook</h4>
                    <div class="paragraph">
                        <p>如果操作符在执行其 <code>onNext</code>、<code>onError</code> 以及 <code>onComplete</code> 方法的时候抛出异常，那么
                            <code>onOperatorError</code> 这一个 hook 会被调用。</p>
                    </div>
                    <div class="paragraph">
                        <p>与上一类 hook 不同，这个 hook 还是处在正常的执行路径中的。一个典型的例子就是包含一个 map 函数式的
                            <code>map</code> 操作符抛出的异常（比如零作为除数），这时候还是会执行到 <code>onError</code> 的。</p>
                    </div>
                    <div class="paragraph">
                        <p>首先，它会将异常传递给 <code>onOperatorError</code>。利用这个 hook 你可以检查这个错误（以及有问题的相关数据），
                            并可以 <em>改变</em> 这个异常。当然你还可以做些别的事情，比如记录日志或返回原始异常。</p>
                    </div>
                    <div class="paragraph">
                        <p>注意，<code>onOperatorError</code> hook 也可以被多次设置：你可以提供一个 <code>String</code> 为一个特别的
                            <code>BiFunction</code> 类型的函数式设置识别符，不同识别符的函数式都会被执行，当然，重复使用一个识别符的话，
                            则后来的设置会覆盖前边的设置。</p>
                    </div>
                    <div class="paragraph">
                        <p>因此，默认的 hook 可以使用 <code>Hooks.resetOnOperatorError()</code> 方法重置，而提供识别符的 hook 可以使用
                            <code>Hooks.resetOnOperatorError(String)</code> 方法来重置。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="hooks-assembly"><a class="anchor" href="#hooks-assembly"></a>8.7.3. 组装 Hooks</h4>
                    <div class="paragraph">
                        <p>这些组装（assembly） hooks 关联了操作符的生命周期。它们会在一个操作链被组装起来的时候（即实例化的时候）
                            被调用。每一个新的操作符组装到操作链上的时候，<code>onEachOperator</code> 都会返回一个不同的发布者，
                            从而可以利用它动态调整操作符。<code>onLastOperator</code> 与之类似，不过只会在被操作链上的最后一个
                            （<code>subscribe</code> 调用之前的）操作符调用。</p>
                    </div>
                    <div class="paragraph">
                        <p>类似于 <code>onOperatorError</code>，也可以叠加，并且通过识别符来标识。也是用类似的方式重置全部或部分 hooks。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_预置_hooks"><a class="anchor" href="#_预置_hooks"></a>8.7.4. 预置 Hooks</h4>
                    <div class="paragraph">
                        <p><code>Hooks</code> 工具类还提供了一些预置的 hooks。利用他们可以改变一些默认的处理方式，而不用自己
                            编写 hook：</p>
                    </div>
                    <div class="ulist">
                        <ul>
                            <li>
                                <p><code>onNextDroppedFail()</code>：<code>onNextDropped</code> 通常会抛出 <code>Exceptions.failWithCancel()</code>
                                    异常。
                                    现在它默认还会以 DEBUG 级别对被丢弃的值记录日志。如果想回到原来的只是抛出异常的方式，使用
                                    <code>onNextDroppedFail()</code>。</p>
                            </li>
                            <li>
                                <p><code>onOperatorDebug()</code>: 这个方法会激活 <a href="#debug-activate">debug mode</a>。它与
                                    <code>onOperatorError</code>
                                    hook 关联，所以调用 <code>resetOnOperatorError()</code> 同时也会重置它。不过它内部也用到了特别的识别符，
                                    你可以通过 <code>resetOnOperatorDebug()</code> 方法来重置它。</p>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="context"><a class="anchor" href="#context"></a>8.8. 增加一个 Context 到响应式序列</h3>
                <div class="paragraph">
                    <p>当从命令式编程风格切换到响应式编程风格的时候，一个技术上最大的挑战就是线程处理。</p>
                </div>
                <div class="paragraph">
                    <p>与习惯做法不同的是，在响应式编程中，一个线程（<code>Thread</code>）可以被用于处理多个同时运行的异步序列
                        （实际上是非阻塞的）。执行过程也会经常从一个线程切换到另一个线程。</p>
                </div>
                <div class="paragraph">
                    <p>这样的情况下，对于开发者来说，如果依赖线程模型中相对“稳定”的特性——比如 <code>ThreadLocal</code>
                        ——就会变得很难。因为它会让你将数据绑定到一个 <strong>线程</strong> 上，所以在响应式环境中使用就变得
                        比较困难。因此，将使用了 <code>ThreadLocal</code> 的库应用于 Reactor 的时候就会带来新的挑战。通常会更糟，
                        它用起来效果会更差，甚至会失败。 比如，使用 Logback 的 MDC 来存储日志关联的 ID，就是一个非常符合
                        这种情况的例子。</p>
                </div>
                <div class="paragraph">
                    <p>通常的对 <code>ThreadLocal</code> 的替代方案是将环境相关的数据 <code>C</code>，同业务数据 <code>T</code> 一起置于序列中,
                        比如使用 <code>Tuple2&lt;T, C&gt;</code>。这种方案看起来并不好，况且会在方法和 <code>Flux</code> 泛型中暴露环境数据信息。</p>
                </div>
                <div class="paragraph">
                    <p>自从版本 <code>3.1.0</code>，Reactor 引入了一个类似于 <code>ThreadLocal</code> 的高级功能：<code>Context</code>。它作用于一个
                        <code>Flux</code> 或一个 <code>Mono</code> 上，而不是应用于一个线程（<code>Thread</code>）。</p>
                </div>
                <div class="paragraph">
                    <p>为了说明，这里有个读写 <code>Context</code> 的简单例子：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="predefined-type">String</span> key = <span
        class="string"><span class="delimiter">&quot;</span><span class="content">message</span><span class="delimiter">&quot;</span></span>;
Mono&lt;<span class="predefined-type">String</span>&gt; r = Mono.just(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Hello</span><span class="delimiter">&quot;</span></span>)
                .flatMap( s -&gt; Mono.subscriberContext()
                                   .map( ctx -&gt; s + <span class="string"><span class="delimiter">&quot;</span><span
            class="content"> </span><span class="delimiter">&quot;</span></span> + ctx.get(key)))
                .subscriberContext(ctx -&gt; ctx.put(key, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">World</span><span
            class="delimiter">&quot;</span></span>));

StepVerifier.create(r)
            .expectNext(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Hello World</span><span class="delimiter">&quot;</span></span>)
            .verifyComplete();</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>接下来的几个小节，我们来了解 <code>Context</code> 是什么以及如何用，从而最终可以理解上边的例子。</p>
                </div>
                <div class="admonitionblock important">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-important" title="Important"></i>
                            </td>
                            <td class="content">
                                这是一个主要面向库开发人员的高级功能。这需要开发者对 <code>Subscription</code> 的生命周期
                                充分理解，并且明白它主要用于 subscription 相关的库。
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="sect3">
                    <h4 id="_code_context_code_api"><a class="anchor" href="#_code_context_code_api"></a>8.8.1. <code>Context</code>
                        API</h4>
                    <div class="paragraph">
                        <p><code>Context</code> 是一个类似于 <code>Map</code>（这种数据结构）的接口：它存储键值（key-value）对，你需要通过 key 来获取值：</p>
                    </div>
                    <div class="ulist">
                        <ul>
                            <li>
                                <p>key 和 value 都是 <code>Object</code> 类型，所以 <code>Context</code> 可以包含任意数量的任意对象。</p>
                            </li>
                            <li>
                                <p><code>Context</code> 是 <strong>不可变的（immutable）</strong>。</p>
                            </li>
                            <li>
                                <p>用 <code>put(Object key, Object value)</code> 方法来存储一个键值对，返回一个新的 <code>Context</code>
                                    对象。
                                    你也可以用 <code>putAll(Context)</code> 方法将两个 context 合并为一个新的 context。</p>
                            </li>
                            <li>
                                <p>用 <code>hasKey(Object key)</code> 方法检查一个 key 是否已经存在。</p>
                            </li>
                            <li>
                                <p>用 <code>getOrDefault(Object key, T defaultValue)</code> 方法取回 key 对应的值（类型转换为
                                    <code>T</code>），
                                    或在找不到这个 key 的情况下返回一个默认值。</p>
                            </li>
                            <li>
                                <p>用 <code>getOrEmpty(Object key)</code> 来得到一个 <code>Optional&lt;T&gt;</code> （context
                                    会尝试将值转换为 <code>T</code>）。</p>
                            </li>
                            <li>
                                <p>用 <code>delete(Object key)</code> 来删除 key 关联的值，并返回一个新的 <code>Context</code>。</p>
                            </li>
                        </ul>
                    </div>
                    <div class="admonitionblock tip">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-tip" title="Tip"></i>
                                </td>
                                <td class="content">
                                    <strong>创建一个</strong> <code>Context</code> 时，你可以用静态方法 <code>Context.of</code> 预先存储最多
                                    5 个键值对。
                                    它接受 2, 4, 6, 8 或 10 个 <code>Object</code> 对象，两两一对作为键值对添加到 <code>Context</code>。<br>
                                    <br>
                                    你也可以用 <code>Context.empty()</code> 方法来创建一个空的 <code>Context</code>。
                                </td>
                            </tr>
                        </table>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_把_code_context_code_绑定到_code_flux_code_and_writing"><a class="anchor"
                                                                                    href="#_把_code_context_code_绑定到_code_flux_code_and_writing"></a>8.8.2.
                        把 <code>Context</code> 绑定到 <code>Flux</code> and Writing</h4>
                    <div class="paragraph">
                        <p>为了使用 context，它必须要绑定到一个指定的序列，并且链上的每个操作符都可以访问它。
                            注意，这里的操作符必须是 Reactor 内置的操作符，因为 <code>Context</code> 是 Reactor 特有的。</p>
                    </div>
                    <div class="paragraph">
                        <p>实际上，一个 <code>Context</code> 是绑定到每一个链中的 <code>Subscriber</code> 上的。 它使用
                            <code>Subscription</code>
                            的传播机制来让自己对每一个操作符都可见（从最后一个 <code>subscribe</code> 沿链向上）。</p>
                    </div>
                    <div class="paragraph">
                        <p>为了填充 <code>Context</code> ——只能在订阅时（subscription time）——你需要使用 <code>subscriberContext</code>
                            操作符。</p>
                    </div>
                    <div class="paragraph">
                        <p><code>subscriberContext(Context)</code> 方法会将你提供的 <code>Context</code>
                            与来自下游（记住，<code>Context</code> 是从下游
                            向上游传播的）的 <code>Context`合并。 这通过调用 `putAll</code> 实现，最后会生成一个新的 <code>Context</code> 给上游。</p>
                    </div>
                    <div class="admonitionblock tip">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-tip" title="Tip"></i>
                                </td>
                                <td class="content">
                                    你也可以用更高级的 <code>subscriberContext(Function&lt;Context, Context&gt;)</code>。它接受来自下游的
                                    <code>Context</code>，然后你可以根据需要添加或删除值，然后返回新的 <code>Context</code>。你甚至可以返回一个完全不同
                                    的对象，虽然不太建议这样（这样可能影响到依赖这个 <code>Context</code> 的库）。
                                </td>
                            </tr>
                        </table>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_读取_context"><a class="anchor" href="#_读取_context"></a>8.8.3. 读取 Context</h4>
                    <div class="paragraph">
                        <p>填充 <code>Context</code> 是一方面，读取数据同样重要。多数时候，添加内容到 <code>Context</code> 是最终用户的责任，
                            但是利用这些信息是库的责任，因为库通常是客户代码的上游。</p>
                    </div>
                    <div class="paragraph">
                        <p>读取 context 数据使用静态方法 <code>Mono.subscriberContext()</code>。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_简单的例子"><a class="anchor" href="#_简单的例子"></a>8.8.4. 简单的例子</h4>
                    <div class="paragraph">
                        <p>本例的初衷是为了让你对如何使用 <code>Context</code> 有个更好的理解。</p>
                    </div>
                    <div class="paragraph">
                        <p>让我们先回头看一下最初的例子：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="predefined-type">String</span> key = <span
        class="string"><span class="delimiter">&quot;</span><span class="content">message</span><span class="delimiter">&quot;</span></span>;
Mono&lt;<span class="predefined-type">String</span>&gt; r = Mono.just(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Hello</span><span class="delimiter">&quot;</span></span>)
                .flatMap( s -&gt; Mono.subscriberContext() <i class="conum" data-value="2"></i><b>(2)</b>
                                   .map( ctx -&gt; s + <span class="string"><span class="delimiter">&quot;</span><span
            class="content"> </span><span class="delimiter">&quot;</span></span> + ctx.get(key))) <i class="conum"
                                                                                                     data-value="3"></i><b>(3)</b>
                .subscriberContext(ctx -&gt; ctx.put(key, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">World</span><span
            class="delimiter">&quot;</span></span>)); <i class="conum" data-value="1"></i><b>(1)</b>

StepVerifier.create(r)
            .expectNext(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Hello World</span><span class="delimiter">&quot;</span></span>) <i class="conum"
                                                                                               data-value="4"></i><b>(4)</b>
            .verifyComplete();</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>操作链以调用 <code>subscriberContext(Function)</code> 结尾，将 <code>"World"</code> 作为 <code>"message"</code>
                                    这个
                                    key 的 值添加到 <code>Context</code> 中。
                                </td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>对源调用 <code>flatMap</code> 用 <code>Mono.subscriberContext()</code> 方法拿到
                                    <code>Context</code>。
                                </td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>然后使用 <code>map</code> 读取关联到 <code>"message"</code> 的值，然后与原来的值连接。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="4"></i><b>4</b></td>
                                <td>最后 <code>Mono&lt;String&gt;</code> 确实发出了 <code>"Hello World"</code>。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="admonitionblock important">
                        <table>
                            <tr>
                                <td class="icon">
                                    <i class="fa icon-important" title="Important"></i>
                                </td>
                                <td class="content">
                                    上边的数字顺序并不是按照代码行顺序排的，这并非错误：它代表了执行顺序。虽然
                                    <code>subscriberContext</code> 是链上的最后一个环节，但确实最先执行的（原因在于 subscription 信号
                                    是从下游向上的）。
                                </td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>注意在你的操作链中，<strong>写入</strong> 与 <strong>读取</strong> <code>Context</code> 的
                            <strong>相对位置</strong> 很重要：因为
                            <code>Context</code> 是不可变的，它的内容只能被上游的操作符看到，如下例所示：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="predefined-type">String</span> key = <span
        class="string"><span class="delimiter">&quot;</span><span class="content">message</span><span class="delimiter">&quot;</span></span>;
Mono&lt;<span class="predefined-type">String</span>&gt; r = Mono.just(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Hello</span><span class="delimiter">&quot;</span></span>)
                     .subscriberContext(ctx -&gt; ctx.put(key, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">World</span><span
            class="delimiter">&quot;</span></span>)) <i class="conum" data-value="1"></i><b>(1)</b>
                     .flatMap( s -&gt; Mono.subscriberContext()
                                        .map( ctx -&gt; s + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> </span><span class="delimiter">&quot;</span></span> + ctx.getOrDefault(key, <span
            class="string"><span class="delimiter">&quot;</span><span class="content">Stranger</span><span
            class="delimiter">&quot;</span></span>)));  <i class="conum" data-value="2"></i><b>(2)</b>

StepVerifier.create(r)
            .expectNext(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Hello Stranger</span><span class="delimiter">&quot;</span></span>) <i class="conum"
                                                                                                  data-value="3"></i><b>(3)</b>
            .verifyComplete();</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>写入 <code>Context</code> 的位置太靠上了&#8230;&#8203;</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>所以在 <code>flatMap</code> 就没有 key 关联的值，使用了默认值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>结果 <code>Mono&lt;String&gt;</code> 发出了 <code>"Hello Stranger"</code>。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>下面的例子同样说明了 <code>Context</code> 的不可变性（<code>Mono.subscriberContext()</code> 总是返回由 <code>subscriberContext</code>
                            配置的 <code>Context</code>）：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="predefined-type">String</span> key = <span
        class="string"><span class="delimiter">&quot;</span><span class="content">message</span><span class="delimiter">&quot;</span></span>;

Mono&lt;<span class="predefined-type">String</span>&gt; r = Mono.subscriberContext() <i class="conum"
                                                                                        data-value="1"></i><b>(1)</b>
        .map( ctx -&gt; ctx.put(key, <span class="string"><span class="delimiter">&quot;</span><span class="content">Hello</span><span
            class="delimiter">&quot;</span></span>)) <i class="conum" data-value="2"></i><b>(2)</b>
        .flatMap( ctx -&gt; Mono.subscriberContext()) <i class="conum" data-value="3"></i><b>(3)</b>
        .map( ctx -&gt; ctx.getOrDefault(key,<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Default</span><span class="delimiter">&quot;</span></span>)); <i class="conum"
                                                                                             data-value="4"></i><b>(4)</b>

StepVerifier.create(r)
        .expectNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">Default</span><span
            class="delimiter">&quot;</span></span>) <i class="conum" data-value="5"></i><b>(5)</b>
        .verifyComplete();</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>拿到 <code>Context</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>在 <code>map</code> 方法中我们尝试修改它。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>在 <code>flatMap</code> 中再次获取 <code>Context</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="4"></i><b>4</b></td>
                                <td>读取 <code>Context</code> 中可能的值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="5"></i><b>5</b></td>
                                <td>值从来没有被设置为 <code>"Hello"</code>。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>类似的，如果多次对 <code>Context</code> 中的同一个 key 赋值的话，要看 <strong>写入的相对顺序</strong> ：
                            读取 <code>Context</code> 的操作符只能拿到下游最近的一次写入的值，如下例所示：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="predefined-type">String</span> key = <span
        class="string"><span class="delimiter">&quot;</span><span class="content">message</span><span class="delimiter">&quot;</span></span>;
Mono&lt;<span class="predefined-type">String</span>&gt; r = Mono.just(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Hello</span><span class="delimiter">&quot;</span></span>)
                .flatMap( s -&gt; Mono.subscriberContext()
                                   .map( ctx -&gt; s + <span class="string"><span class="delimiter">&quot;</span><span
            class="content"> </span><span class="delimiter">&quot;</span></span> + ctx.get(key)))
                .subscriberContext(ctx -&gt; ctx.put(key, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">Reactor</span><span
            class="delimiter">&quot;</span></span>)) <i class="conum" data-value="1"></i><b>(1)</b>
                .subscriberContext(ctx -&gt; ctx.put(key, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">World</span><span
            class="delimiter">&quot;</span></span>)); <i class="conum" data-value="2"></i><b>(2)</b>

StepVerifier.create(r)
            .expectNext(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Hello Reactor</span><span class="delimiter">&quot;</span></span>) <i class="conum"
                                                                                                 data-value="3"></i><b>(3)</b>
            .verifyComplete();</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>写入 <code>"message"</code> 的值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>另一次写入 <code>"message"</code> 的值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td><code>map</code> 方法值能拿到下游最近的一次写入的值： <code>"Reactor"</code>。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>这里，首先 <code>Context</code> 中的 key 被赋值 <code>"World"</code>。然后订阅信号（subscription signal）向上游
                            移动，又发生了另一次写入。这次生成了第二个不变的 <code>Context</code>，里边的值是 <code>"Reactor"</code>。之后，
                            数据开始流动， <code>flatMap</code> 拿到最近的 <code>Context</code> ，也就是第二个值为 <code>Reactor</code> 的
                            <code>Context</code>。</p>
                    </div>
                    <div class="paragraph">
                        <p>你可能会觉得 <code>Context</code> 是与数据信号一块传播的。如果是那样的话，在两次写入操作中间加入的一个
                            <code>flatMap</code> 会使用最上游的这个 <code>Context</code>。但并不是这样的，如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="predefined-type">String</span> key = <span
        class="string"><span class="delimiter">&quot;</span><span class="content">message</span><span class="delimiter">&quot;</span></span>;
Mono&lt;<span class="predefined-type">String</span>&gt; r = Mono.just(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Hello</span><span class="delimiter">&quot;</span></span>)
                     .flatMap( s -&gt; Mono.subscriberContext()
                                        .map( ctx -&gt; s + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> </span><span class="delimiter">&quot;</span></span> + ctx.get(key))) <i
            class="conum" data-value="3"></i><b>(3)</b>
                     .subscriberContext(ctx -&gt; ctx.put(key, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">Reactor</span><span
            class="delimiter">&quot;</span></span>)) <i class="conum" data-value="2"></i><b>(2)</b>
                     .flatMap( s -&gt; Mono.subscriberContext()
                                        .map( ctx -&gt; s + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> </span><span class="delimiter">&quot;</span></span> + ctx.get(key))) <i
            class="conum" data-value="4"></i><b>(4)</b>
                     .subscriberContext(ctx -&gt; ctx.put(key, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">World</span><span
            class="delimiter">&quot;</span></span>)); <i class="conum" data-value="1"></i><b>(1)</b>

StepVerifier.create(r)
            .expectNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">Hello Reactor World</span><span
            class="delimiter">&quot;</span></span>) <i class="conum" data-value="5"></i><b>(5)</b>
            .verifyComplete();</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>这里是第一次赋值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>这里是第二次赋值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>第一个 <code>flatMap</code> 看到的是第二次的赋值。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="4"></i><b>4</b></td>
                                <td>第二个 <code>flatMap</code> 将上一个的结果与 <strong>第一次赋值</strong> 的 context 值连接。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="5"></i><b>5</b></td>
                                <td><code>Mono</code> 发出的是 <code>"Hello Reactor World"</code>。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>原因在于 <code>Context</code> 是与 <code>Subscriber</code> 关联的，而每一个操作符访问的 <code>Context</code>
                            来自其下游的 <code>Subscriber</code>。</p>
                    </div>
                    <div class="paragraph">
                        <p>最后一个有意思的传播方式是，对 <code>Context</code> 的赋值也可以在一个 <code>flatMap</code> <strong>内部</strong>，如下：
                        </p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="predefined-type">String</span> key = <span
        class="string"><span class="delimiter">&quot;</span><span class="content">message</span><span class="delimiter">&quot;</span></span>;
Mono&lt;<span class="predefined-type">String</span>&gt; r =
        Mono.just(<span class="string"><span class="delimiter">&quot;</span><span class="content">Hello</span><span
            class="delimiter">&quot;</span></span>)
            .flatMap( s -&gt; Mono.subscriberContext()
                               .map( ctx -&gt; s + <span class="string"><span class="delimiter">&quot;</span><span
            class="content"> </span><span class="delimiter">&quot;</span></span> + ctx.get(key))
            )
            .flatMap( s -&gt; Mono.subscriberContext()
                               .map( ctx -&gt; s + <span class="string"><span class="delimiter">&quot;</span><span
            class="content"> </span><span class="delimiter">&quot;</span></span> + ctx.get(key))
                               .subscriberContext(ctx -&gt; ctx.put(key, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">Reactor</span><span class="delimiter">&quot;</span></span>)) <i class="conum"
                                                                                            data-value="1"></i><b>(1)</b>
            )
            .subscriberContext(ctx -&gt; ctx.put(key, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">World</span><span class="delimiter">&quot;</span></span>)); <i class="conum"
                                                                                           data-value="2"></i><b>(2)</b>

StepVerifier.create(r)
            .expectNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">Hello World Reactor</span><span
            class="delimiter">&quot;</span></span>)
            .verifyComplete();</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>这个 <code>subscriberContext</code> 不会影响所在 <code>flatMap</code> 之外的任何东西。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>这个 <code>subscriberContext</code> 会影响主序列的 <code>Context</code>。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>上边的例子中，最后发出的值是 <code>"Hello World Reactor"</code> 而不是 "Hello Reactor World"，因为赋值
                            "Reactor" 的 <code>subscriberContext</code> 是作用于第二个 <code>flatMap</code> 的内部序列的。所以不会在主序列可见/
                            传播，第一个 <code>flatMap</code> 也看不到它。传播（Propagation） + 不可变性（immutability）将类似
                            <code>flatMap</code> 这样的操作符中的创建的内部序列中的 <code>Context</code> 与外部隔离开来。</p>
                    </div>
                </div>
                <div class="sect3">
                    <h4 id="_完整的例子"><a class="anchor" href="#_完整的例子"></a>8.8.5. 完整的例子</h4>
                    <div class="paragraph">
                        <p>让我们来看一个实际的从 <code>Context</code> 中读取值的例子：一个响应式的 HTTP 客户端将一个 <code>Mono&lt;String&gt;</code>
                            （用于 <code>PUT</code> 请求）作为数据源，同时通过一个特定的 key 使用 Context 将关联的ID信息放入请求头中。</p>
                    </div>
                    <div class="paragraph">
                        <p>从用户角度，是这样调用的：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
                            <pre class="CodeRay highlight"><code data-lang="java">doPut(<span class="string"><span
                                    class="delimiter">&quot;</span><span class="content">www.example.com</span><span
                                    class="delimiter">&quot;</span></span>, Mono.just(<span class="string"><span
                                    class="delimiter">&quot;</span><span class="content">Walter</span><span
                                    class="delimiter">&quot;</span></span>))</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>为了传播一个关联ID，应该这样调用：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">doPut(<span class="string"><span
        class="delimiter">&quot;</span><span class="content">www.example.com</span><span class="delimiter">&quot;</span></span>, Mono.just(<span
        class="string"><span class="delimiter">&quot;</span><span class="content">Walter</span><span class="delimiter">&quot;</span></span>))
        .subscriberContext(<span class="predefined-type">Context</span>.of(HTTP_CORRELATION_ID, <span
            class="string"><span class="delimiter">&quot;</span><span class="content">2-j3r9afaf92j-afkaf</span><span
            class="delimiter">&quot;</span></span>))</code></pre>
                        </div>
                    </div>
                    <div class="paragraph">
                        <p>由上可见，用户代码使用了 <code>subscriberContext</code> 来为 <code>Context</code> 的 <code>HTTP_CORRELATION_ID</code>
                            赋值。上游的操作符是一个由 HTTP 客户端库返回的 <code>Mono&lt;Tuple2&lt;Integer, String&gt;&gt;</code>
                            （一个简化的 HTTP 响应）。所以能够正确将信息从用户代码传递给库代码。</p>
                    </div>
                    <div class="paragraph">
                        <p>下边的例子演示了从库的角度由 context 读取值的模拟代码，如果能够找到关联ID，则“增加请求”：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="directive">static</span> <span class="directive">final</span> <span
        class="predefined-type">String</span> HTTP_CORRELATION_ID = <span class="string"><span
        class="delimiter">&quot;</span><span class="content">reactive.http.library.correlationId</span><span
        class="delimiter">&quot;</span></span>;

Mono&lt;Tuple2&lt;<span class="predefined-type">Integer</span>, <span class="predefined-type">String</span>&gt;&gt; doPut(<span
            class="predefined-type">String</span> url, Mono&lt;<span class="predefined-type">String</span>&gt; data) {
        Mono&lt;Tuple2&lt;<span class="predefined-type">String</span>, Optional&lt;<span
            class="predefined-type">Object</span>&gt;&gt;&gt; dataAndContext =
                        data.zipWith(Mono.subscriberContext() <i class="conum" data-value="1"></i><b>(1)</b>
                                         .map(c -&gt; c.getOrEmpty(HTTP_CORRELATION_ID))); <i class="conum"
                                                                                              data-value="2"></i><b>(2)</b>

        <span class="keyword">return</span> dataAndContext
                        .&lt;<span class="predefined-type">String</span>&gt;handle((dac, sink) -&gt; {
                                <span class="keyword">if</span> (dac.getT2().isPresent()) { <i class="conum"
                                                                                               data-value="3"></i><b>(3)</b>
                                        sink.next(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">PUT &lt;</span><span class="delimiter">&quot;</span></span> + dac.getT1() + <span
            class="string"><span class="delimiter">&quot;</span><span class="content">&gt; sent to </span><span
            class="delimiter">&quot;</span></span> + url + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> with header X-Correlation-ID = </span><span
            class="delimiter">&quot;</span></span> + dac.getT2().get());
                                }
                                <span class="keyword">else</span> {
                                        sink.next(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">PUT &lt;</span><span class="delimiter">&quot;</span></span> + dac.getT1() + <span
            class="string"><span class="delimiter">&quot;</span><span class="content">&gt; sent to </span><span
            class="delimiter">&quot;</span></span> + url);
                                }
                                sink.complete();
                        })
                        .map(msg -&gt; Tuples.of(<span class="integer">200</span>, msg));
}</code></pre>
                        </div>
                    </div>
                    <div class="colist arabic">
                        <table>
                            <tr>
                                <td><i class="conum" data-value="1"></i><b>1</b></td>
                                <td>用 <code>Mono.subscriberContext()</code> 拿到 <code>Context</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="2"></i><b>2</b></td>
                                <td>提取出关联ID的值——是一个 <code>Optional</code>。</td>
                            </tr>
                            <tr>
                                <td><i class="conum" data-value="3"></i><b>3</b></td>
                                <td>如果值存在，那么就将其加入请求头。</td>
                            </tr>
                        </table>
                    </div>
                    <div class="paragraph">
                        <p>在这段库代码片段中，你可以看到它是如何将 <code>Mono</code> 和 <code>Mono.subscriberContext()</code> zip 起来的。
                            返回的是一个 <code>Tuple2&lt;String, Context&gt;</code>，这个 <code>Context</code> 包含来自下游的 <code>HTTP_CORRELATION_ID</code>
                            的值。</p>
                    </div>
                    <div class="paragraph">
                        <p>库代码接着用 <code>map</code> 读取出那个 key 的值 <code>Optional&lt;String&gt;</code>，如果值存在，将其作为 <code>X-Correlation-ID</code>
                            请求头。
                            最后一块而用 <code>handle</code> 来处理。</p>
                    </div>
                    <div class="paragraph">
                        <p>用来验证上边的库代码的测试程序如下：</p>
                    </div>
                    <div class="listingblock">
                        <div class="content">
<pre class="CodeRay highlight"><code data-lang="java"><span class="annotation">@Test</span>
<span class="directive">public</span> <span class="type">void</span> contextForLibraryReactivePut() {
        Mono&lt;<span class="predefined-type">String</span>&gt; put = doPut(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">www.example.com</span><span
            class="delimiter">&quot;</span></span>, Mono.just(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">Walter</span><span class="delimiter">&quot;</span></span>))
                        .subscriberContext(<span class="predefined-type">Context</span>.of(HTTP_CORRELATION_ID, <span
            class="string"><span class="delimiter">&quot;</span><span class="content">2-j3r9afaf92j-afkaf</span><span
            class="delimiter">&quot;</span></span>))
                        .filter(t -&gt; t.getT1() &lt; <span class="integer">300</span>)
                        .map(Tuple2::getT2);

        StepVerifier.create(put)
                    .expectNext(<span class="string"><span class="delimiter">&quot;</span><span class="content">PUT &lt;Walter&gt; sent to www.example.com with header X-Correlation-ID = 2-j3r9afaf92j-afkaf</span><span
            class="delimiter">&quot;</span></span>)
                    .verifyComplete();
}</code></pre>
                        </div>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="null-safety"><a class="anchor" href="#null-safety"></a>8.9. 空值安全</h3>
                <div class="paragraph">
                    <p>虽然 Java 的类型系统没有表达空值安全（null-safety）的机制，但是 Reactor 现在提供了基于注解的用于声明
                        “可能为空（nullability）”的 API，类似于 Spring Framework 5 中提供的 API。</p>
                </div>
                <div class="paragraph">
                    <p>Reactor 自身就用到了这些注解，你也可以将其用于任何基于 Reactor 的自己的空值安全的 Java API 中。
                        不过，在 <strong>方法体内部</strong> 对“可能为空”的类型的使用就不在这一特性的范围内了。</p>
                </div>
                <div class="paragraph">
                    <p>这些注解是基于 <a href="https://jcp.org/en/jsr/detail?id=305">JSR 305</a> 的注解（是受类似 IntelliJ IDEA
                        这样的工具支持的 JSR）作为元注解（meta-annotated）的。当 Java 开发者在编写空值安全的代码时，
                        它们能够提供有用的警告信息，以便避免在运行时（runtime）出现 <code>NullPointerException</code> 异常。
                        JSR 305 元注解使得工具提供商可以以一种通用的方式提供对空值安全的支持，从而 Reactor
                        的注解就不用重复造轮子了。</p>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                <div class="paragraph">
                                    <p>对于 Kotlin 1.1.5+，需要（同时也推荐）在项目 classpath 中添加对 JSR 305 的依赖。</p>
                                </div>
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>它们也可在 Kotlin 中使用，Kotlin 原生支持
                        <a href="https://kotlinlang.org/docs/reference/null-safety.html">空值安全</a>。具体请参考
                        <a href="#kotlin-null-safety">this dedicated section</a> 。</p>
                </div>
                <div class="paragraph">
                    <p><code>reactor.util.annotation</code> 包提供以下注解：</p>
                </div>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>
                                <a href="https://projectreactor.io/docs/core/release/api/reactor/util/annotation/NonNull.html"><code>@NonNull</code></a>
                                表明一个具体的参数、返回值或域值不能为 <code>null</code>。
                                （如果参数或返回值应用了 <code>@NonNullApi</code> 则无需再加它）。</p>
                        </li>
                        <li>
                            <p>
                                <a href="https://projectreactor.io/docs/core/release/api/reactor/util/annotation/Nullable.html"><code>@Nullable</code></a>
                                表明一个参数、返回值或域值可以为 <code>null</code>。</p>
                        </li>
                        <li>
                            <p>
                                <a href="https://projectreactor.io/docs/core/release/api/reactor/util/annotation/NonNullApi.html"><code>@NonNullApi</code></a>
                                是一个包级别的注解，表明默认情况下参数或返回值不能为 <code>null</code>。</p>
                        </li>
                    </ul>
                </div>
                <div class="admonitionblock note">
                    <table>
                        <tr>
                            <td class="icon">
                                <i class="fa icon-note" title="Note"></i>
                            </td>
                            <td class="content">
                                <div class="paragraph">
                                    <p>（Reactor 的空值安全的注解）对于通用类型参数（generic type arguments）、可变参数（varargs），以及数组元素（array
                                        elements）
                                        尚不支持。参考 <a href="https://github.com/reactor/reactor-core/issues/878">issue
                                            #878</a> 查看最新信息。</p>
                                </div>
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p><a class="fa fa-edit"
                          href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/advancedFeatures.adoc"
                          title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                        - "<a href="#advanced">高级特性与概念</a>"</p>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="which-operator"><a class="anchor" href="#which-operator"></a>Appendix A: 我需要哪个操作符？</h2>
        <div class="sectionbody">
            <div class="paragraph">
                <p>TIP：在这一节，如果一个操作符是专属于 <code>Flux</code> 或 <code>Mono</code> 的，那么会给它注明前缀。
                    公共的操作符没有前缀。如果一个具体的用例涉及多个操作符的组合，这里以方法调用的方式展现，
                    会以一个点（.）开头，并将参数置于圆括号内，比如： <code>.methodCall(parameter)</code>。</p>
            </div>
            <div class="paragraph">
                <p>我想搞定：</p>
            </div>
            <div class="ulist">
                <ul>
                    <li>
                        <p><a href="#which.create">创建一个新序列，它&#8230;&#8203;</a></p>
                    </li>
                    <li>
                        <p><a href="#which.values">对序列进行转化</a></p>
                    </li>
                    <li>
                        <p><a href="#which.filtering">过滤序列</a></p>
                    </li>
                    <li>
                        <p><a href="#which.peeking">“窥视”（只读）序列</a></p>
                    </li>
                    <li>
                        <p><a href="#which.errors">错误处理</a></p>
                    </li>
                    <li>
                        <p><a href="#which.time">基于时间的操作</a></p>
                    </li>
                    <li>
                        <p><a href="#which.window">拆分 <code>Flux</code></a></p>
                    </li>
                    <li>
                        <p><a href="#which.blocking">回到同步的世界</a></p>
                    </li>
                </ul>
            </div>
            <div class="sect2">
                <h3 id="which.create"><a class="anchor" href="#which.create"></a>A.1. 创建一个新序列，它&#8230;&#8203;</h3>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>发出一个 <code>T</code>，我已经有了：<code>just</code></p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>&#8230;&#8203;基于一个 <code>Optional&lt;T&gt;</code>：<code>Mono#justOrEmpty(Optional&lt;T&gt;)</code>
                                        </p>
                                    </li>
                                    <li>
                                        <p>&#8230;&#8203;基于一个可能为 <code>null</code> 的 T：<code>Mono#justOrEmpty(T)</code>
                                        </p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>发出一个 <code>T</code>，且还是由 <code>just</code> 方法返回</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>&#8230;&#8203;但是“懒”创建的：使用 <code>Mono#fromSupplier</code> 或用
                                            <code>defer</code> 包装 <code>just</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>发出许多 <code>T</code>，这些元素我可以明确列举出来：<code>Flux#just(T...)</code></p>
                        </li>
                        <li>
                            <p>基于迭代数据结构:</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>一个数组：<code>Flux#fromArray</code></p>
                                    </li>
                                    <li>
                                        <p>一个集合或 iterable：<code>Flux#fromIterable</code></p>
                                    </li>
                                    <li>
                                        <p>一个 Integer 的 range：<code>Flux#range</code></p>
                                    </li>
                                    <li>
                                        <p>一个 <code>Stream</code>
                                            提供给每一个订阅：<code>Flux#fromStream(Supplier&lt;Stream&gt;)</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>基于一个参数值给出的源：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>一个 <code>Supplier&lt;T&gt;</code>：<code>Mono#fromSupplier</code></p>
                                    </li>
                                    <li>
                                        <p>一个任务：<code>Mono#fromCallable</code>，<code>Mono#fromRunnable</code></p>
                                    </li>
                                    <li>
                                        <p>一个 <code>CompletableFuture&lt;T&gt;</code>：<code>Mono#fromFuture</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>直接完成：<code>empty</code></p>
                        </li>
                        <li>
                            <p>立即生成错误：<code>error</code></p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>&#8230;&#8203;但是“懒”的方式生成 <code>Throwable</code>：<code>error(Supplier&lt;Throwable&gt;)</code>
                                        </p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>什么都不做：<code>never</code></p>
                        </li>
                        <li>
                            <p>订阅时才决定：<code>defer</code></p>
                        </li>
                        <li>
                            <p>依赖一个可回收的资源：<code>using</code></p>
                        </li>
                        <li>
                            <p>可编程地生成事件（可以使用状态）:</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>同步且逐个的：<code>Flux#generate</code></p>
                                    </li>
                                    <li>
                                        <p>异步（也可同步）的，每次尽可能多发出元素：<code>Flux#create</code>
                                            （<code>Mono#create</code> 也是异步的，只不过只能发一个）</p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="sect2">
                <h3 id="which.values"><a class="anchor" href="#which.values"></a>A.2. 对序列进行转化</h3>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>我想转化一个序列：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>1对1地转化（比如字符串转化为它的长度）：<code>map</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;类型转化：<code>cast</code></p>
                                                </li>
                                                <li>
                                                    <p>&#8230;&#8203;为了获得每个元素的序号：<code>Flux#index</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>1对n地转化（如字符串转化为一串字符）：<code>flatMap</code> + 使用一个工厂方法</p>
                                    </li>
                                    <li>
                                        <p>1对n地转化可自定义转化方法和/或状态：<code>handle</code></p>
                                    </li>
                                    <li>
                                        <p>对每一个元素执行一个异步操作（如对 url 执行 http 请求）：<code>flatMap</code> + 一个异步的返回类型为 <code>Publisher</code>
                                            的方法</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;忽略一些数据：在 flatMap lambda 中根据条件返回一个 <code>Mono.empty()</code>
                                                    </p>
                                                </li>
                                                <li>
                                                    <p>&#8230;&#8203;保留原来的序列顺序：<code>Flux#flatMapSequential</code>（对每个元素的异步任务会立即执行，但会将结果按照原序列顺序排序）
                                                    </p>
                                                </li>
                                                <li>
                                                    <p>&#8230;&#8203;当 Mono
                                                        元素的异步任务会返回多个元素的序列时：<code>Mono#flatMapMany</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想添加一些数据元素到一个现有的序列：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>在开头添加：<code>Flux#startWith(T...)</code></p>
                                    </li>
                                    <li>
                                        <p>在最后添加：<code>Flux#concatWith(T...)</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想将 <code>Flux</code> 转化为集合（一下都是针对 <code>Flux</code> 的）</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>转化为 List：<code>collectList</code>，<code>collectSortedList</code></p>
                                    </li>
                                    <li>
                                        <p>转化为 Map：<code>collectMap</code>，<code>collectMultiMap</code></p>
                                    </li>
                                    <li>
                                        <p>转化为自定义集合：<code>collect</code></p>
                                    </li>
                                    <li>
                                        <p>计数：<code>count</code></p>
                                    </li>
                                    <li>
                                        <p>reduce 算法（将上个元素的reduce结果与当前元素值作为输入执行reduce方法，如sum） <code>reduce</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;将每次 reduce 的结果立即发出：<code>scan</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>转化为一个 boolean 值：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>对所有元素判断都为true：<code>all</code></p>
                                                </li>
                                                <li>
                                                    <p>对至少一个元素判断为true：<code>any</code></p>
                                                </li>
                                                <li>
                                                    <p>判断序列是否有元素（不为空）：<code>hasElements</code></p>
                                                </li>
                                                <li>
                                                    <p>判断序列中是否有匹配的元素：<code>hasElement</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想合并 publishers&#8230;&#8203;</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>按序连接：<code>Flux#concat</code> 或 <code>.concatWith(other)</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;即使有错误，也会等所有的 publishers 连接完成：<code>Flux#concatDelayError</code>
                                                    </p>
                                                </li>
                                                <li>
                                                    <p>&#8230;&#8203;按订阅顺序连接（这里的合并仍然可以理解成序列的连接）：<code>Flux#mergeSequential</code>
                                                    </p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>按元素发出的顺序合并（无论哪个序列的，元素先到先合并）：<code>Flux#merge</code> /
                                            <code>.mergeWith(other)</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;元素类型会发生变化：<code>Flux#zip</code> / <code>Flux#zipWith</code>
                                                    </p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>将元素组合：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>2个 Monos 组成1个 <code>Tuple2</code>：<code>Mono#zipWith</code></p>
                                                </li>
                                                <li>
                                                    <p>n个 Monos 的元素都发出来后组成一个 Tuple：<code>Mono#zip</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>在终止信号出现时“采取行动”：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>在 Mono 终止时转换为一个
                                                        <code>Mono&lt;Void&gt;</code>：<code>Mono#and</code></p>
                                                </li>
                                                <li>
                                                    <p>当 n 个 Mono 都终止时返回
                                                        <code>Mono&lt;Void&gt;</code>：<code>Mono#when</code></p>
                                                </li>
                                                <li>
                                                    <p>返回一个存放组合数据的类型，对于被合并的多个序列：</p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>每个序列都发出一个元素时：<code>Flux#zip</code></p>
                                                            </li>
                                                            <li>
                                                                <p>任何一个序列发出元素时：<code>Flux#combineLatest</code></p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>只取各个序列的第一个元素：<code>Flux#first</code>，<code>Mono#first</code>，<code>mono.or
                                            (otherMono).or(thirdMono)</code>，`flux.or(otherFlux).or(thirdFlux)</p>
                                    </li>
                                    <li>
                                        <p>由一个序列触发（类似于 <code>flatMap</code>，不过“喜新厌旧”）：<code>switchMap</code></p>
                                    </li>
                                    <li>
                                        <p>由每个新序列开始时触发（也是“喜新厌旧”风格）：<code>switchOnNext</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想重复一个序列：<code>repeat</code></p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>&#8230;&#8203;但是以一定的间隔重复：<code>Flux.interval(duration).flatMap(tick -&gt;
                                            myExistingPublisher)</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我有一个空序列，但是&#8230;&#8203;</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>我想要一个缺省值来代替：<code>defaultIfEmpty</code></p>
                                    </li>
                                    <li>
                                        <p>我想要一个缺省的序列来代替：<code>switchIfEmpty</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我有一个序列，但是我对序列的元素值不感兴趣：<code>ignoreElements</code></p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>&#8230;&#8203;并且我希望用 <code>Mono</code> 来表示序列已经结束：<code>then</code></p>
                                    </li>
                                    <li>
                                        <p>&#8230;&#8203;并且我想在序列结束后等待另一个任务完成：<code>thenEmpty</code></p>
                                    </li>
                                    <li>
                                        <p>&#8230;&#8203;并且我想在序列结束之后返回一个 <code>Mono</code>：<code>Mono#then(mono)</code>
                                        </p>
                                    </li>
                                    <li>
                                        <p>&#8230;&#8203;并且我想在序列结束之后返回一个值：<code>Mono#thenReturn(T)</code></p>
                                    </li>
                                    <li>
                                        <p>&#8230;&#8203;并且我想在序列结束之后返回一个 <code>Flux</code>：<code>thenMany</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我有一个 Mono 但我想延迟完成&#8230;&#8203;</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>&#8230;&#8203;当有1个或N个其他 publishers
                                            都发出（或结束）时才完成：<code>Mono#delayUntilOther</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;使用一个函数式来定义如何获取“其他 publisher”：<code>Mono#delayUntil(Function)</code>
                                                    </p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想基于一个递归的生成序列的规则扩展每一个元素，然后合并为一个序列发出：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>&#8230;&#8203;广度优先：<code>expand(Function)</code></p>
                                    </li>
                                    <li>
                                        <p>&#8230;&#8203;深度优先：<code>expandDeep(Function)</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="sect2">
                <h3 id="which.peeking"><a class="anchor" href="#which.peeking"></a>A.3. “窥视”（只读）序列</h3>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>再不对序列造成改变的情况下，我想：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>得到通知或执行一些操作：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>发出元素：<code>doOnNext</code></p>
                                                </li>
                                                <li>
                                                    <p>序列完成：<code>Flux#doOnComplete</code>，<code>Mono#doOnSuccess</code>
                                                    </p>
                                                </li>
                                                <li>
                                                    <p>因错误终止：<code>doOnError</code></p>
                                                </li>
                                                <li>
                                                    <p>取消：<code>doOnCancel</code></p>
                                                </li>
                                                <li>
                                                    <p>订阅时：<code>doOnSubscribe</code></p>
                                                </li>
                                                <li>
                                                    <p>请求时：<code>doOnRequest</code></p>
                                                </li>
                                                <li>
                                                    <p>完成或错误终止：<code>doOnTerminate</code>（Mono的方法可能包含有结果）</p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>但是在终止信号向下游传递 <strong>之后</strong> ：<code>doAfterTerminate</code>
                                                                </p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                                <li>
                                                    <p>所有类型的信号（<code>Signal</code>）：<code>Flux#doOnEach</code></p>
                                                </li>
                                                <li>
                                                    <p>所有结束的情况（完成complete、错误error、取消cancel）：<code>doFinally</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>记录日志：<code>log</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想知道所有的事件:</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>每一个事件都体现为一个 <code>single</code> 对象：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>执行 callback：<code>doOnEach</code></p>
                                                </li>
                                                <li>
                                                    <p>每个元素转化为 <code>single</code> 对象：<code>materialize</code></p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>&#8230;&#8203;在转化回元素：<code>dematerialize</code></p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>转化为一行日志：<code>log</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="sect2">
                <h3 id="which.filtering"><a class="anchor" href="#which.filtering"></a>A.4. 过滤序列</h3>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>我想过滤一个序列</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>基于给定的判断条件：<code>filter</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;异步地进行判断：<code>filterWhen</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>仅限于指定类型的对象：<code>ofType</code></p>
                                    </li>
                                    <li>
                                        <p>忽略所有元素：<code>ignoreElements</code></p>
                                    </li>
                                    <li>
                                        <p>去重:</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>对于整个序列：<code>Flux#distinct</code></p>
                                                </li>
                                                <li>
                                                    <p>去掉连续重复的元素：<code>Flux#distinctUntilChanged</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我只想要一部分序列：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>只要 N 个元素：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>从序列的第一个元素开始算：<code>Flux#take(long)</code></p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>
                                                                    &#8230;&#8203;取一段时间内发出的元素：<code>Flux#take(Duration)</code>
                                                                </p>
                                                            </li>
                                                            <li>
                                                                <p>&#8230;&#8203;只取第一个元素放到 <code>Mono</code> 中返回：<code>Flux#next()</code>
                                                                </p>
                                                            </li>
                                                            <li>
                                                                <p>&#8230;&#8203;使用 <code>request(N)</code> 而不是取消：<code>Flux#limitRequest(long)</code>
                                                                </p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                                <li>
                                                    <p>从序列的最后一个元素倒数：<code>Flux#takeLast</code></p>
                                                </li>
                                                <li>
                                                    <p>直到满足某个条件（包含）：<code>Flux#takeUntil</code>（基于判断条件），<code>Flux#takeUntilOther</code>（基于对
                                                        publisher 的比较）</p>
                                                </li>
                                                <li>
                                                    <p>直到满足某个条件（不包含）：<code>Flux#takeWhile</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>最多只取 1 个元素：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>给定序号：<code>Flux#elementAt</code></p>
                                                </li>
                                                <li>
                                                    <p>最后一个：<code>.takeLast(1)</code></p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>&#8230;&#8203;如果为序列空则发出错误信号：<code>Flux#last()</code>
                                                                </p>
                                                            </li>
                                                            <li>
                                                                <p>&#8230;&#8203;如果序列为空则返回默认值：<code>Flux#last(T)</code>
                                                                </p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>跳过一些元素：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>从序列的第一个元素开始跳过：<code>Flux#skip(long)</code></p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>
                                                                    &#8230;&#8203;跳过一段时间内发出的元素：<code>Flux#skip(Duration)</code>
                                                                </p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                                <li>
                                                    <p>跳过最后的 n 个元素：<code>Flux#skipLast</code></p>
                                                </li>
                                                <li>
                                                    <p>直到满足某个条件（包含）：<code>Flux#skipUntil</code>（基于判断条件），<code>Flux#skipUntilOther</code>
                                                        （基于对 publisher 的比较）</p>
                                                </li>
                                                <li>
                                                    <p>直到满足某个条件（不包含）：<code>Flux#skipWhile</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>采样：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>给定采样周期：<code>Flux#sample(Duration)</code></p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>取采样周期里的第一个元素而不是最后一个：<code>sampleFirst</code></p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                                <li>
                                                    <p>基于另一个 publisher：<code>Flux#sample(Publisher)</code></p>
                                                </li>
                                                <li>
                                                    <p>基于 publisher“超时”：<code>Flux#sampleTimeout</code> （每一个元素会触发一个
                                                        publisher，如果这个 publisher 不被下一个元素触发的 publisher 覆盖就发出这个元素）</p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我只想要一个元素（如果多于一个就返回错误）&#8230;&#8203;</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>如果序列为空，发出错误信号：<code>Flux#single()</code></p>
                                    </li>
                                    <li>
                                        <p>如果序列为空，发出一个缺省值：<code>Flux#single(T)</code></p>
                                    </li>
                                    <li>
                                        <p>如果序列为空就返回一个空序列：<code>Flux#singleOrEmpty</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="sect2">
                <h3 id="which.errors"><a class="anchor" href="#which.errors"></a>A.5. 错误处理</h3>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>我想创建一个错误序列：<code>error</code>&#8230;&#8203;</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>&#8230;&#8203;替换一个完成的 <code>Flux</code>：<code>.concat(Flux.error(e))</code>
                                        </p>
                                    </li>
                                    <li>
                                        <p>&#8230;&#8203;替换一个完成的 <code>Mono</code>：<code>.then(Mono.error(e))</code></p>
                                    </li>
                                    <li>
                                        <p>&#8230;&#8203;如果元素超时未发出：<code>timeout</code></p>
                                    </li>
                                    <li>
                                        <p>&#8230;&#8203;“懒”创建：<code>error(Supplier&lt;Throwable&gt;)</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想要类似 try/catch 的表达方式：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>抛出异常：<code>error</code></p>
                                    </li>
                                    <li>
                                        <p>捕获异常：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>然后返回缺省值：<code>onErrorReturn</code></p>
                                                </li>
                                                <li>
                                                    <p>然后返回一个 <code>Flux</code> 或
                                                        <code>Mono</code>：<code>onErrorResume</code></p>
                                                </li>
                                                <li>
                                                    <p>包装异常后再抛出：<code>.onErrorMap(t -&gt; new
                                                        RuntimeException(t))</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>finally 代码块：<code>doFinally</code></p>
                                    </li>
                                    <li>
                                        <p>Java 7 之后的 try-with-resources 写法：<code>using</code> 工厂方法</p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想从错误中恢复&#8230;&#8203;</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>返回一个缺省的：</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>的值：<code>onErrorReturn</code></p>
                                                </li>
                                                <li>
                                                    <p><code>Publisher</code>：<code>Flux#onErrorResume</code> 和 <code>Mono#onErrorResume</code>
                                                    </p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>重试：<code>retry</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;由一个用于伴随 Flux 触发：<code>retryWhen</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想处理回压错误（向上游发出“MAX”的 request，如果下游的 request 比较少，则应用策略）&#8230;&#8203;</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>抛出 <code>IllegalStateException</code>：<code>Flux#onBackpressureError</code>
                                        </p>
                                    </li>
                                    <li>
                                        <p>丢弃策略：<code>Flux#onBackpressureDrop</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;但是不丢弃最后一个元素：<code>Flux#onBackpressureLatest</code>
                                                    </p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>缓存策略（有限或无限）：<code>Flux#onBackpressureBuffer</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>
                                                        &#8230;&#8203;当有限的缓存空间用满则应用给定策略：<code>Flux#onBackpressureBuffer</code>
                                                        带有策略 <code>BufferOverflowStrategy</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                </ul>
                            </div>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="sect2">
                <h3 id="which.time"><a class="anchor" href="#which.time"></a>A.6. 基于时间的操作</h3>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>我想将元素转换为带有时间信息的 <code>Tuple2&lt;Long, T&gt;</code>&#8230;&#8203;</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>从订阅时开始：<code>elapsed</code></p>
                                    </li>
                                    <li>
                                        <p>记录时间戳：<code>timestamp</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>如果元素间延迟过长则中止序列：<code>timeout</code></p>
                        </li>
                        <li>
                            <p>以固定的周期发出元素：<code>Flux#interval</code></p>
                        </li>
                        <li>
                            <p>在一个给定的延迟后发出 <code>0</code>：static <code>Mono.delay</code>.</p>
                        </li>
                        <li>
                            <p>我想引入延迟：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>对每一个元素：<code>Mono#delayElement</code>，<code>Flux#delayElements</code></p>
                                    </li>
                                    <li>
                                        <p>延迟订阅：<code>delaySubscription</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="sect2">
                <h3 id="which.window"><a class="anchor" href="#which.window"></a>A.7. 拆分 <code>Flux</code></h3>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>我想将一个 <code>Flux&lt;T&gt;</code> 拆分为一个 <code>Flux&lt;Flux&lt;T&gt;&gt;</code>：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>以个数为界：<code>window(int)</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;会出现重叠或丢弃的情况：<code>window(int, int)</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>以时间为界：<code>window(Duration)</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;会出现重叠或丢弃的情况：<code>window(Duration, Duration)</code>
                                                    </p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>以个数或时间为界：<code>windowTimeout(int, Duration)</code></p>
                                    </li>
                                    <li>
                                        <p>基于对元素的判断条件：<code>windowUntil</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;触发判断条件的元素会分到下一波（<code>cutBefore</code> 变量）：<code>.windowUntil(predicate,
                                                        true)</code></p>
                                                </li>
                                                <li>
                                                    <p>
                                                        &#8230;&#8203;满足条件的元素在一波，直到不满足条件的元素发出开始下一波：<code>windowWhile</code>
                                                        （不满足条件的元素会被丢弃）</p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>通过另一个 Publisher 的每一个 onNext 信号来拆分序列：<code>window(Publisher)</code>，<code>windowWhen</code>
                                        </p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想将一个 <code>Flux&lt;T&gt;</code> 的元素拆分到集合&#8230;&#8203;</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>拆分为一个一个的 <code>List</code>:</p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>以个数为界：<code>buffer(int)</code></p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>&#8230;&#8203;会出现重叠或丢弃的情况：<code>buffer(int,
                                                                    int)</code></p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                                <li>
                                                    <p>以时间为界：<code>buffer(Duration)</code></p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>&#8230;&#8203;会出现重叠或丢弃的情况：<code>buffer(Duration,
                                                                    Duration)</code></p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                                <li>
                                                    <p>以个数或时间为界：<code>bufferTimeout(int, Duration)</code></p>
                                                </li>
                                                <li>
                                                    <p>基于对元素的判断条件：<code>bufferUntil(Predicate)</code></p>
                                                    <div class="ulist">
                                                        <ul>
                                                            <li>
                                                                <p>&#8230;&#8203;触发判断条件的元素会分到下一个buffer：<code>.bufferUntil(predicate,
                                                                    true)</code></p>
                                                            </li>
                                                            <li>
                                                                <p>&#8230;&#8203;满足条件的元素在一个buffer，直到不满足条件的元素发出开始下一buffer：<code>bufferWhile(Predicate)</code>
                                                                </p>
                                                            </li>
                                                        </ul>
                                                    </div>
                                                </li>
                                                <li>
                                                    <p>通过另一个 Publisher 的每一个 onNext
                                                        信号来拆分序列：<code>buffer(Publisher)</code>，<code>bufferWhen</code>
                                                    </p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>拆分到指定类型的 "collection"：<code>buffer(int, Supplier&lt;C&gt;)</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我想将 <code>Flux&lt;T&gt;</code> 中具有共同特征的元素分组到子
                                Flux：<code>groupBy(Function&lt;T,K&gt;)</code>
                                TIP：注意返回值是 <code>Flux&lt;GroupedFlux&lt;K, T&gt;&gt;</code>，每一个 <code>GroupedFlux</code>
                                具有相同的 key 值 <code>K</code>，可以通过 <code>key()</code> 方法获取。</p>
                        </li>
                    </ul>
                </div>
            </div>
            <div class="sect2">
                <h3 id="which.blocking"><a class="anchor" href="#which.blocking"></a>A.8. 回到同步的世界</h3>
                <div class="ulist">
                    <ul>
                        <li>
                            <p>我有一个 <code>Flux&lt;T&gt;</code>，我想：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>在拿到第一个元素前阻塞：<code>Flux#blockFirst</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;并给出超时时限：<code>Flux#blockFirst(Duration)</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>在拿到最后一个元素前阻塞（如果序列为空则返回 null）：<code>Flux#blockLast</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;并给出超时时限：<code>Flux#blockLast(Duration)</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>同步地转换为 <code>Iterable&lt;T&gt;</code>：<code>Flux#toIterable</code></p>
                                    </li>
                                    <li>
                                        <p>同步地转换为 Java 8 <code>Stream&lt;T&gt;</code>：<code>Flux#toStream</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                        <li>
                            <p>我有一个 <code>Mono&lt;T&gt;</code>，我想：</p>
                            <div class="ulist">
                                <ul>
                                    <li>
                                        <p>在拿到元素前阻塞：<code>Mono#block</code></p>
                                        <div class="ulist">
                                            <ul>
                                                <li>
                                                    <p>&#8230;&#8203;并给出超时时限：<code>Mono#block(Duration)</code></p>
                                                </li>
                                            </ul>
                                        </div>
                                    </li>
                                    <li>
                                        <p>转换为 <code>CompletableFuture&lt;T&gt;</code>：<code>Mono#toFuture</code></p>
                                    </li>
                                </ul>
                            </div>
                        </li>
                    </ul>
                </div>
                <div class="paragraph">
                    <p><a class="fa fa-edit"
                          href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/apdx-operatorChoice.adoc"
                          title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                        - "<a href="#which-operator">我需要哪个操作符？</a>"</p>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="faq"><a class="anchor" href="#faq"></a>Appendix B: FAQ，最佳实践，以及“我如何&#8230;&#8203;?”</h2>
        <div class="sectionbody">
            <div class="sect2">
                <h3 id="faq.wrap-blocking"><a class="anchor" href="#faq.wrap-blocking"></a>B.1. 如何包装一个同步阻塞的调用？</h3>
                <div class="paragraph">
                    <p>很多时候，信息源是同步和阻塞的。在 Reactor 中，我们用以下方式处理这种信息源：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Mono blockingWrapper = Mono.fromCallable(() -&gt; { <i
        class="conum" data-value="1"></i><b>(1)</b>
    <span class="keyword">return</span> <span class="comment">/* make a remote synchronous call */</span> <i
            class="conum" data-value="2"></i><b>(2)</b>
});
blockingWrapper = blockingWrapper.subscribeOn(Schedulers.elastic()); <i class="conum"
                                                                        data-value="3"></i><b>(3)</b></code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>使用 <code>fromCallable</code> 方法生成一个 Mono；</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>返回同步、阻塞的资源；</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>使用 <code>Schedulers.elastic()</code> 确保对每一个订阅来说运行在一个专门的线程上。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>因为调用返回一个值，所以你应该使用 Mono。你应该使用 <code>Schedulers.elastic</code>
                        因为它会创建一个专门的线程来等待阻塞的调用返回。</p>
                </div>
                <div class="paragraph">
                    <p>注意 <code>subscribeOn</code> 方法并不会“订阅”这个 <code>Mono</code>。它只是指定了订阅操作使用哪个 <code>Scheduler</code>。
                    </p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="faq.chain"><a class="anchor" href="#faq.chain"></a>B.2. 用在 <code>Flux</code> 上的操作符好像没起作用，为啥？
                </h3>
                <div class="paragraph">
                    <p>请确认你确实对调用 <code>.subscribe()</code> 的发布者应用了这个操作符。</p>
                </div>
                <div class="paragraph">
                    <p>Reactor 的操作符是装饰器（decorators）。它们会返回一个不同的（发布者）实例，
                        这个实例对上游序列进行了包装并增加了一些的处理行为。所以，最推荐的方式是将操作符“串”起来。</p>
                </div>
                <div class="paragraph">
                    <p>对比下边的两个例子：</p>
                </div>
                <div class="listingblock">
                    <div class="title">没有串起来（不正确的）</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux = Flux.just(<span
        class="string"><span class="delimiter">&quot;</span><span class="content">foo</span><span class="delimiter">&quot;</span></span>, <span
        class="string"><span class="delimiter">&quot;</span><span class="content">chain</span><span class="delimiter">&quot;</span></span>);
flux.map(secret -&gt; secret.replaceAll(<span class="string"><span class="delimiter">&quot;</span><span class="content">.</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">*</span><span class="delimiter">&quot;</span></span>)); <i class="conum" data-value="1"></i><b>(1)</b>
flux.subscribe(next -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Received: </span><span class="delimiter">&quot;</span></span> + next));</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>问题在这， <code>flux</code> 变量并没有改变。</td>
                        </tr>
                    </table>
                </div>
                <div class="listingblock">
                    <div class="title">串起来（正确的）</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux = Flux.just(<span
        class="string"><span class="delimiter">&quot;</span><span class="content">foo</span><span class="delimiter">&quot;</span></span>, <span
        class="string"><span class="delimiter">&quot;</span><span class="content">chain</span><span class="delimiter">&quot;</span></span>);
flux = flux.map(secret -&gt; secret.replaceAll(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">.</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">*</span><span class="delimiter">&quot;</span></span>));
flux.subscribe(next -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Received: </span><span class="delimiter">&quot;</span></span> + next));</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>下边的例子更好（因为更简洁）：</p>
                </div>
                <div class="listingblock">
                    <div class="title">串起来（最好的）</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; secrets = Flux
  .just(<span class="string"><span class="delimiter">&quot;</span><span class="content">foo</span><span
            class="delimiter">&quot;</span></span>, <span class="string"><span class="delimiter">&quot;</span><span
            class="content">chain</span><span class="delimiter">&quot;</span></span>)
  .map(secret -&gt; secret.replaceAll(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">.</span><span class="delimiter">&quot;</span></span>, <span class="string"><span
            class="delimiter">&quot;</span><span class="content">*</span><span class="delimiter">&quot;</span></span>))
  .subscribe(next -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">Received: </span><span class="delimiter">&quot;</span></span> + next));</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>第一个例子的输出：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code>Received: foo
Received: chain</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>后两个例子的输出：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code>Received: ***
Received: *****</code></pre>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="faq.monoThen"><a class="anchor" href="#faq.monoThen"></a>B.3. <code>Mono</code>
                    <code>zipWith</code>/<code>zipWhen</code> 没有被调用</h3>
                <div class="listingblock">
                    <div class="title">例子</div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">myMethod.process(<span class="string"><span class="delimiter">&quot;</span><span
        class="content">a</span><span class="delimiter">&quot;</span></span>) <span class="comment">// 这个方法返回 Mono&lt;Void&gt;</span>
        .zipWith(myMethod.process(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">b</span><span class="delimiter">&quot;</span></span>), combinator) <span class="comment">//没有被调用</span>
        .subscribe();</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>如果源 <code>Mono</code> 为空或是一个 <code>Mono&lt;Void&gt;</code>（<code>Mono&lt;Void&gt;</code>
                        通常用于“空”的场景），
                        下边的组合操作就不会被调用。</p>
                </div>
                <div class="paragraph">
                    <p>对于类似 <code>zipWith</code> 的用于转换的操作符来说，这是比较典型的场景。
                        这些操作符依赖于数据元素来转换为输出的元素。
                        如果任何一个序列是空的，则返回的就是一个空序列，所以请谨慎使用。
                        例如在 <code>then()</code> 之后使用 <code>zipWith()</code> 就会导致这一问题。</p>
                </div>
                <div class="paragraph">
                    <p>对于以 <code>Function</code> 作为参数的 <code>and</code> 更是如此，因为返回的 Mono
                        是依赖于收到的数据懒加载的（而对于空序列或 <code>Void</code> 的序列来说是没有数据发出来的）。</p>
                </div>
                <div class="paragraph">
                    <p>你可以使用 <code>.defaultIfEmpty(T)</code> 将空序列替换为包含 <code>T</code> 类型缺省值的序列（而不是 <code>Void</code>
                        序列），
                        从而可以避免类似的情况出现。举例如下：</p>
                </div>
                <div class="listingblock">
                    <div class="title">在 <code>zipWhen</code> 前使用 <code>defaultIfEmpty</code></div>
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">myMethod.emptySequenceForKey(<span class="string"><span
        class="delimiter">&quot;</span><span class="content">a</span><span class="delimiter">&quot;</span></span>) <span
        class="comment">// 这个方法返回一个空的 Mono&lt;String&gt;</span>
        .defaultIfEmpty(<span class="string"><span class="delimiter">&quot;</span><span class="delimiter">&quot;</span></span>) <span
            class="comment">// 将空序列转换为包含字符串 &quot;&quot; 的序列</span>
        .zipWhen(aString -&gt; myMethod.process(<span class="string"><span class="delimiter">&quot;</span><span
            class="content">b</span><span class="delimiter">&quot;</span></span>)) <span class="comment">// 当 &quot;&quot; 发出时被调用</span>
        .subscribe();</code></pre>
                    </div>
                </div>
            </div>
            <div class="sect2">
                <h3 id="faq.retryWhen"><a class="anchor" href="#faq.retryWhen"></a>B.4. 如何用 <code>retryWhen</code> 来实现
                    <code>retry(3)</code> 的效果？</h3>
                <div class="paragraph">
                    <p><code>retryWhen</code> 方法比较复杂，希望下边的一段模拟 <code>retry(3)</code> 的代码能够帮你更好地理解它的工作方式：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux =
Flux.&lt;<span class="predefined-type">String</span>&gt;error(<span class="keyword">new</span> <span class="exception">IllegalArgumentException</span>())
    .retryWhen(companion -&gt; companion
    .zipWith(Flux.range(<span class="integer">1</span>, <span class="integer">4</span>), <i class="conum"
                                                                                            data-value="1"></i><b>(1)</b>
          (error, index) -&gt; { <i class="conum" data-value="2"></i><b>(2)</b>
            <span class="keyword">if</span> (index &lt; <span class="integer">4</span>) <span
            class="keyword">return</span> index; <i class="conum" data-value="3"></i><b>(3)</b>
            <span class="keyword">else</span> <span class="keyword">throw</span> Exceptions.propagate(error); <i
            class="conum" data-value="4"></i><b>(4)</b>
          })
    );</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>技巧一：使用 <code>zip</code> 和一个“重试个数 + 1”的 <code>range</code>。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td><code>zip</code> 方法让你可以在对重试次数计数的同时，仍掌握着原始的错误（error）。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>允许三次重试，小于 4 的时候发出一个值。</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>为了使序列以错误结束。我们将原始异常在三次重试之后抛出。</td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="faq.exponentialBackoff"><a class="anchor" href="#faq.exponentialBackoff"></a>B.5. 如何使用 <code>retryWhen</code>
                    进行 exponential backoff？</h3>
                <div class="paragraph">
                    <p>Exponential backoff 的意思是进行的多次重试之间的间隔越来越长，
                        从而避免对源系统造成过载，甚至宕机。基本原理是，如果源产生了一个错误，
                        那么已经是处于不稳定状态，可能不会立刻复原。所以，如果立刻就重试可能会产生另一个错误，
                        导致源更加不稳定。</p>
                </div>
                <div class="paragraph">
                    <p>下面是一段实现 exponential backoff 效果的例子，每次重试的间隔都会递增
                        （伪代码： delay = attempt number * 100 milliseconds）：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">Flux&lt;<span class="predefined-type">String</span>&gt; flux =
Flux.&lt;<span class="predefined-type">String</span>&gt;error(<span class="keyword">new</span> <span class="exception">IllegalArgumentException</span>())
    .retryWhen(companion -&gt; companion
        .doOnNext(s -&gt; <span class="predefined-type">System</span>.out.println(s + <span class="string"><span
            class="delimiter">&quot;</span><span class="content"> at </span><span class="delimiter">&quot;</span></span> + LocalTime.now())) <i
            class="conum" data-value="1"></i><b>(1)</b>
        .zipWith(Flux.range(<span class="integer">1</span>, <span class="integer">4</span>), (error, index) -&gt; { <i
            class="conum" data-value="2"></i><b>(2)</b>
          <span class="keyword">if</span> (index &lt; <span class="integer">4</span>) <span
            class="keyword">return</span> index;
          <span class="keyword">else</span> <span class="keyword">throw</span> Exceptions.propagate(error);
        })
        .flatMap(index -&gt; Mono.delay(<span class="predefined-type">Duration</span>.ofMillis(index * <span
            class="integer">100</span>))) <i class="conum" data-value="3"></i><b>(3)</b>
        .doOnNext(s -&gt; <span class="predefined-type">System</span>.out.println(<span class="string"><span
            class="delimiter">&quot;</span><span class="content">retried at </span><span class="delimiter">&quot;</span></span> + LocalTime.now())) <i
            class="conum" data-value="4"></i><b>(4)</b>
    );</code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>记录错误出现的时间；</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>使用 <code>retryWhen</code> + <code>zipWith</code> 的技巧实现重试3次的效果；</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>通过 <code>flatMap</code> 来实现延迟时间递增的效果；</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="4"></i><b>4</b></td>
                            <td>同样记录重试的时间。</td>
                        </tr>
                    </table>
                </div>
                <div class="paragraph">
                    <p>订阅它，输出如下：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre>java.lang.IllegalArgumentException at 18:02:29.338
retried at 18:02:29.459 <i class="conum" data-value="1"></i><b>(1)</b>
java.lang.IllegalArgumentException at 18:02:29.460
retried at 18:02:29.663 <i class="conum" data-value="2"></i><b>(2)</b>
java.lang.IllegalArgumentException at 18:02:29.663
retried at 18:02:29.964 <i class="conum" data-value="3"></i><b>(3)</b>
java.lang.IllegalArgumentException at 18:02:29.964</pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>第一次重试延迟大约 100ms</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="2"></i><b>2</b></td>
                            <td>第二次重试延迟大约 200ms</td>
                        </tr>
                        <tr>
                            <td><i class="conum" data-value="3"></i><b>3</b></td>
                            <td>第三次重试延迟大约 300ms</td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="_how_do_i_ensure_thread_affinity_using_code_publishon_code"><a class="anchor"
                                                                                       href="#_how_do_i_ensure_thread_affinity_using_code_publishon_code"></a>B.6.
                    How do I ensure thread affinity using <code>publishOn()</code>?</h3>
                <div class="paragraph">
                    <p>如 <a href="#schedulers">Schedulers</a> 所述，<code>publishOn()</code> 可以用来切换执行线程。
                        <code>publishOn</code> 能够影响到其之后的操作符的执行线程，直到有新的 <code>publishOn</code> 出现。
                        所以 <code>publishOn</code> 的位置很重要。</p>
                </div>
                <div class="paragraph">
                    <p>比如下边的例子， <code>map()</code> 中的 <code>transform</code> 方法是在 <code>scheduler1</code> 的一个工作线程上执行的，
                        而 <code>doOnNext()</code> 中的 <code>processNext</code> 方法是在 <code>scheduler2</code> 的一个工作线程上执行的。
                        单线程的调度器可能用于对不同阶段的任务或不同的订阅者确保线程关联性。</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">EmitterProcessor&lt;<span class="predefined-type">Integer</span>&gt; processor = EmitterProcessor.create();
processor.publishOn(scheduler1)
         .map(i -&gt; transform(i))
         .publishOn(scheduler2)
         .doOnNext(i -&gt; processNext(i))
         .subscribe();</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p><a class="fa fa-edit"
                          href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/faq.adoc"
                          title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                        - "<a href="#faq">FAQ，最佳实践，以及“我如何&#8230;&#8203;?”</a>"</p>
                </div>
            </div>
        </div>
    </div>
    <div class="sect1">
        <h2 id="reactor-extra"><a class="anchor" href="#reactor-extra"></a>Appendix C: Reactor-Extra</h2>
        <div class="sectionbody">
            <div class="paragraph">
                <p><code>reactor-extra</code> 为满足 <code>reactor-core</code> 用户的更高级需求，提供了一些额外的操作符和工具。</p>
            </div>
            <div class="paragraph">
                <p>由于这是一个单独的包，使用时需要明确它的依赖：</p>
            </div>
            <div class="listingblock">
                <div class="content">
<pre class="CodeRay highlight"><code data-lang="groovy">dependencies {
     compile <span class="string"><span class="delimiter">'</span><span
            class="content">io.projectreactor:reactor-core</span><span class="delimiter">'</span></span>
     compile <span class="string"><span class="delimiter">'</span><span class="content">io.projectreactor.addons:reactor-extra</span><span
            class="delimiter">'</span></span> <i class="conum" data-value="1"></i><b>(1)</b>
}</code></pre>
                </div>
            </div>
            <div class="colist arabic">
                <table>
                    <tr>
                        <td><i class="conum" data-value="1"></i><b>1</b></td>
                        <td>添加 reactor-extra 的依赖。参考 <a href="#getting">获取 Reactor</a> 了解为什么使用BOM的情况下不需要指定 version。</td>
                    </tr>
                </table>
            </div>
            <div class="sect2">
                <h3 id="extra-tuples"><a class="anchor" href="#extra-tuples"></a>C.1. <code>TupleUtils</code> 以及函数式接口
                </h3>
                <div class="paragraph">
                    <p>在 Java 8 提供的函数式接口基础上，<code>reactor.function</code>
                        包又提供了一些支持 3 到 8 个值的 <code>Function</code>、<code>Predicate</code> 和 <code>Consumer</code>。</p>
                </div>
                <div class="paragraph">
                    <p><code>TupleUtils</code> 提供的静态方法可以方便地用于将相应的 <code>Tuple</code> 函数式接口的 lambda 转换为更简单的接口。</p>
                </div>
                <div class="paragraph">
                    <p>这使得我们在使用 <code>Tuple</code> 中各成员的时候更加容易，比如：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
<pre class="CodeRay highlight"><code data-lang="java">.map(tuple -&gt; {
  <span class="predefined-type">String</span> firstName = tuple.getT1();
  <span class="predefined-type">String</span> lastName = tuple.getT2();
  <span class="predefined-type">String</span> address = tuple.getT3();

  <span class="keyword">return</span> <span class="keyword">new</span> Customer(firstName, lastName, address);
});</code></pre>
                    </div>
                </div>
                <div class="paragraph">
                    <p>可以用下面的方式代替：</p>
                </div>
                <div class="listingblock">
                    <div class="content">
                        <pre class="CodeRay highlight"><code data-lang="java">.map(TupleUtils.function(Customer::<span
                                class="keyword">new</span>)); <i class="conum"
                                                                 data-value="1"></i><b>(1)</b></code></pre>
                    </div>
                </div>
                <div class="colist arabic">
                    <table>
                        <tr>
                            <td><i class="conum" data-value="1"></i><b>1</b></td>
                            <td>（因为 <code>Customer</code> 的构造方法符合 <code>Consumer3</code> 的函数式接口标签）</td>
                        </tr>
                    </table>
                </div>
            </div>
            <div class="sect2">
                <h3 id="extra-math"><a class="anchor" href="#extra-math"></a>C.2. <code>MathFlux</code> 的数学操作符</h3>
                <div class="paragraph">
                    <p>T`reactor.math` 包的 <code>MathFlux</code> 提供了一些用于数学计算的操作符，如
                        <code>max</code>、<code>min</code>、<code>sumInt</code>、<code>averageDouble</code>&#8230;&#8203;
                    </p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="extra-repeat-retry"><a class="anchor" href="#extra-repeat-retry"></a>C.3. 重复与重试工具</h3>
                <div class="paragraph">
                    <p><code>reactor.retry</code> 包中有一些能够帮助实现 <code>Flux#repeatWhen</code> 和
                        <code>Flux#retryWhen</code> 的工具。入口点（entry points）就是 <code>Repeat</code> 和 <code>Retry</code>
                        接口的工厂方法。</p>
                </div>
                <div class="paragraph">
                    <p>两个接口都可用作可变的构建器（mutative builder），并且相应的实现（implementing）
                        都可作为 <code>Function</code> 用于对应的操作符。</p>
                </div>
            </div>
            <div class="sect2">
                <h3 id="extra-schedulers"><a class="anchor" href="#extra-schedulers"></a>C.4. 调度器</h3>
                <div class="paragraph">
                    <p>Reactor-extra 提供了若干专用的调度器：
                        - <code>ForkJoinPoolScheduler</code>，位于 <code>reactor.scheduler.forkjoin</code> 包；
                        - <code>SwingScheduler</code>，位于 <code>reactor.swing</code> 包；
                        - <code>SwtScheduler</code>，位于 <code>reactor.swing</code> 包。</p>
                </div>
                <div class="paragraph">
                    <p><a class="fa fa-edit"
                          href="https://github.com/get-set/reactor-core/edit/master-zh/src/docs/asciidoc/apdx-reactorExtra.adoc"
                          title="通过Github的编辑功能对以上翻译内容提出建议">翻译建议</a>
                        - "<a href="#reactor-extra">Reactor-Extra</a>"</p>
                </div>
            </div>
        </div>
    </div>
</div>
<div id="footer">
    <div id="footer-text">
        Last updated 2019-05-08 05:02:56 +08:00
    </div>
</div>
</body>
</html>